From 942630eb4a40294e4c59262c836dc849098667a8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 10 May 2026 22:09:55 -0400 Subject: [PATCH 001/378] feat(llm): cache-policy auto-placement (#26786) --- packages/llm/README.md | 130 +++++++++ packages/llm/src/cache-policy.ts | 120 ++++++++ packages/llm/src/route/client.ts | 3 +- packages/llm/src/schema/messages.ts | 4 +- packages/llm/src/schema/options.ts | 32 +++ packages/llm/test/cache-policy.test.ts | 262 ++++++++++++++++++ ...ache-control-on-identical-second-call.json | 53 ++++ ...nttokencount-on-identical-second-call.json | 51 ++++ ...ached-tokens-on-identical-second-call.json | 51 ++++ .../anthropic-messages-cache.recorded.test.ts | 7 +- .../bedrock-converse-cache.recorded.test.ts | 3 + .../provider/gemini-cache.recorded.test.ts | 7 +- .../openai-responses-cache.recorded.test.ts | 3 + 13 files changed, 721 insertions(+), 5 deletions(-) create mode 100644 packages/llm/README.md create mode 100644 packages/llm/src/cache-policy.ts create mode 100644 packages/llm/test/cache-policy.test.ts create mode 100644 packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json create mode 100644 packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json create mode 100644 packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json diff --git a/packages/llm/README.md b/packages/llm/README.md new file mode 100644 index 000000000..434ce369f --- /dev/null +++ b/packages/llm/README.md @@ -0,0 +1,130 @@ +# @opencode-ai/llm + +Schema-first LLM core for opencode. One typed request, response, event, and tool language; provider quirks live in adapters, not in calling code. + +```ts +import { Effect } from "effect" +import { LLM, LLMClient } from "@opencode-ai/llm" +import { OpenAI } from "@opencode-ai/llm/providers" + +const model = OpenAI.model("gpt-4o-mini", { apiKey: process.env.OPENAI_API_KEY }) + +const request = LLM.request({ + model, + system: "You are concise.", + prompt: "Say hello in one short sentence.", + generation: { maxTokens: 40 }, +}) + +const program = Effect.gen(function* () { + const response = yield* LLMClient.generate(request) + console.log(response.text) +}) +``` + +Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment. + +## Public API + +- **`LLM.request({...})`** — build a provider-neutral `LLMRequest`. Accepts ergonomic inputs (`system: string`, `prompt: string`) that normalize into the canonical Schema classes. +- **`LLM.generate` / `LLM.stream`** — re-exported from `LLMClient` for one-import use. +- **`LLM.user(...)` / `LLM.assistant(...)` / `LLM.toolMessage(...)`** — message constructors. +- **`LLM.toolCall(...)` / `LLM.toolResult(...)` / `LLM.toolDefinition(...)`** — tool-related parts. +- **`LLMClient.prepare(request)`** — compile a request through protocol body construction, validation, and HTTP preparation without sending. Useful for inspection and testing. +- **`LLMEvent.is.*`** — typed guards (`is.text`, `is.toolCall`, `is.requestFinish`, …) for filtering streams. + +## Caching + +Prompt caching is unified across providers. Mark content with a `CacheHint` and each protocol translates it to its wire format (`cache_control` on Anthropic, `cachePoint` on Bedrock; OpenAI's implicit caching needs no markers). + +### Auto placement + +The simplest path is `cache: "auto"` on the request: + +```ts +LLM.request({ + model, + system, + messages, + tools, + cache: "auto", +}) +``` + +`"auto"` places three breakpoints — last tool definition, last system part, latest user message. The last-user-message boundary is the load-bearing detail: in a tool-use loop, a single user turn expands into many assistant/tool round-trips, all sharing that prefix. Caching at that boundary lets every intra-turn API call hit. + +On OpenAI and Gemini `"auto"` is a no-op (their wire formats don't accept inline markers — both use implicit caching). On Anthropic and Bedrock it emits provider-native cache markers. + +### Granular policy + +```ts +cache: { + tools?: boolean, + system?: boolean, + messages?: "latest-user-message" | "latest-assistant" | { tail: number }, + ttlSeconds?: number, // ≥ 3600 → 1h on Anthropic/Bedrock; else 5m +} +``` + +### Manual hints + +Inline `CacheHint` on any text / system / tool / tool-result part overrides automatic placement. The auto policy preserves manual hints; it only fills gaps. + +```ts +LLM.request({ + model, + system: [ + { type: "text", text: "stable system prompt", cache: { type: "ephemeral" } }, + ], + ... +}) +``` + +### Provider behavior table + +| Protocol | `cache: "auto"` | +|---|---| +| Anthropic Messages | emits up to 3 `cache_control` markers (4-breakpoint cap enforced) | +| Bedrock Converse | emits up to 3 `cachePoint` blocks (4-breakpoint cap enforced) | +| OpenAI Chat / Responses | no-op (implicit caching above 1024 tokens) | +| Gemini | no-op (implicit caching on 2.5+; explicit `CachedContent` is out-of-band) | + +Normalized cache usage is read back into `response.usage.cacheReadInputTokens` and `cacheWriteInputTokens` across every provider. + +## Providers + +Each provider exports a `model(...)` helper that records identity, protocol, capabilities, auth, and defaults. + +```ts +import { Anthropic } from "@opencode-ai/llm/providers" + +const model = Anthropic.model("claude-sonnet-4-6", { + apiKey: process.env.ANTHROPIC_API_KEY, +}) +``` + +Included providers: OpenAI, Anthropic, Google (Gemini), Amazon Bedrock, Azure OpenAI, Cloudflare, GitHub Copilot, OpenRouter, xAI, plus generic OpenAI-compatible helpers for DeepSeek, Cerebras, Groq, Fireworks, Together, etc. + +## Provider options & HTTP overlays + +Three escape hatches in order of stability: + +1. **`generation`** — portable knobs (`maxTokens`, `temperature`, `topP`, `topK`, penalties, seed, stop). +2. **`providerOptions: { : {...} }`** — typed-at-the-facade provider-specific knobs (OpenAI `promptCacheKey`, Anthropic `thinking`, Gemini `thinkingConfig`, OpenRouter routing). +3. **`http: { body, headers, query }`** — last-resort serializable overlays merged into the final HTTP request. Reach for this only when a stable typed path doesn't yet exist. + +Model-level defaults are overridden by request-level values for each axis. + +## Routes + +Adding a new model or deployment is usually 5–15 lines using `Route.make({ protocol, transport, ... })`. The four orthogonal pieces are protocol (body construction + stream parsing), transport (endpoint + auth + framing + encoding), defaults, and capabilities. See `AGENTS.md` for the architectural detail. + +## Effect + +This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` (the default registers every shipped route) for runtime dispatch. The example at `example/tutorial.ts` is a runnable walkthrough. + +## See also + +- `AGENTS.md` — architecture, route construction, contributor guide +- `example/tutorial.ts` — runnable end-to-end walkthrough +- `test/provider/*.test.ts` — fixture-first protocol tests; `*.recorded.test.ts` files cover live cassettes diff --git a/packages/llm/src/cache-policy.ts b/packages/llm/src/cache-policy.ts new file mode 100644 index 000000000..46d924ab5 --- /dev/null +++ b/packages/llm/src/cache-policy.ts @@ -0,0 +1,120 @@ +// Apply an `LLMRequest.cache` policy by injecting `CacheHint`s onto the parts +// the policy designates. Runs once at compile time, before the per-protocol +// body builder, so the existing inline-hint lowering path handles the rest. +// +// The default `"auto"` shape places one breakpoint at the last tool definition, +// one at the last system part, and one at the latest user message. This +// matches what production agent harnesses (LangChain's caching middleware, +// kern-ai's 10x cost-reduction playbook) converge on for tool-use loops: the +// latest user message stays put while a single turn explodes into many +// assistant/tool round-trips, so caching at that boundary lets every +// intra-turn API call hit the prefix. +// +// Manual `cache: CacheHint` placements on individual parts are preserved — +// this function only fills gaps the caller left empty. +import { CacheHint, type CachePolicy, type CachePolicyObject } from "./schema/options" +import { LLMRequest, Message, ToolDefinition, type ContentPart } from "./schema/messages" + +const AUTO: CachePolicyObject = { + tools: true, + system: true, + messages: "latest-user-message", +} + +const NONE: CachePolicyObject = {} + +// Resolution rules: +// - undefined → "none" (opt-in default so the policy never changes wire +// shape for existing callers; downstream code can flip to +// `cache: "auto"` once they audit the placement choices). +// - "auto" → the recommended policy: tools + system + latest user msg. +// - "none" → no auto placement; manual `CacheHint`s still flow. +// - object form → exactly what the caller asked for. +const resolve = (policy: CachePolicy | undefined): CachePolicyObject => { + if (policy === undefined || policy === "none") return NONE + if (policy === "auto") return AUTO + return policy +} + +// Protocols whose wire format ignores inline cache markers (OpenAI's implicit +// prefix caching, Gemini's implicit + out-of-band CachedContent). Skip the +// whole policy pass for these — emitting hints would be harmless but pointless. +const RESPECTS_INLINE_HINTS = new Set(["anthropic-messages", "bedrock-converse"]) + +const makeHint = (ttlSeconds: number | undefined): CacheHint => + ttlSeconds !== undefined ? new CacheHint({ type: "ephemeral", ttlSeconds }) : new CacheHint({ type: "ephemeral" }) + +const markLastTool = ( + tools: ReadonlyArray, + hint: CacheHint, +): ReadonlyArray => { + if (tools.length === 0) return tools + const last = tools.length - 1 + if (tools[last]!.cache) return tools + return tools.map((tool, i) => (i === last ? new ToolDefinition({ ...tool, cache: hint }) : tool)) +} + +const markLastSystem = (system: LLMRequest["system"], hint: CacheHint): LLMRequest["system"] => { + if (system.length === 0) return system + const last = system.length - 1 + if (system[last]!.cache) return system + return system.map((part, i) => (i === last ? { ...part, cache: hint } : part)) +} + +const lastIndexOfRole = (messages: ReadonlyArray, role: Message["role"]): number => + messages.findLastIndex((m) => m.role === role) + +// Mark the last text part of `messages[index]`. If no text part exists, mark +// the last content part regardless of type — that's the breakpoint position +// in tool-result-only messages too. +const markMessageAt = ( + messages: ReadonlyArray, + index: number, + hint: CacheHint, +): ReadonlyArray => { + if (index < 0 || index >= messages.length) return messages + const target = messages[index]! + if (target.content.length === 0) return messages + const lastTextIndex = target.content.findLastIndex((part) => part.type === "text") + const markAt = lastTextIndex >= 0 ? lastTextIndex : target.content.length - 1 + const existing = target.content[markAt]! + if ("cache" in existing && existing.cache) return messages + const nextContent = target.content.map((part, i) => + i === markAt ? ({ ...part, cache: hint } as ContentPart) : part, + ) + const next = new Message({ ...target, content: nextContent }) + // Single pass over `messages`, substituting the one updated entry. Long + // conversations call this on every request, so avoid `.map()` here — its + // closure dispatch and identity copies show up in profiling. + const result = messages.slice() + result[index] = next + return result +} + +const markMessages = ( + messages: ReadonlyArray, + strategy: NonNullable, + hint: CacheHint, +): ReadonlyArray => { + if (messages.length === 0) return messages + if (strategy === "latest-user-message") return markMessageAt(messages, lastIndexOfRole(messages, "user"), hint) + if (strategy === "latest-assistant") return markMessageAt(messages, lastIndexOfRole(messages, "assistant"), hint) + const start = Math.max(0, messages.length - strategy.tail) + let next = messages + for (let i = start; i < messages.length; i++) next = markMessageAt(next, i, hint) + return next +} + +export const applyCachePolicy = (request: LLMRequest): LLMRequest => { + if (!RESPECTS_INLINE_HINTS.has(request.model.route)) return request + const policy = resolve(request.cache) + if (!policy.tools && !policy.system && !policy.messages) return request + + const hint = makeHint(policy.ttlSeconds) + const tools = policy.tools ? markLastTool(request.tools, hint) : request.tools + const system = policy.system ? markLastSystem(request.system, hint) : request.system + const messages = policy.messages ? markMessages(request.messages, policy.messages, hint) : request.messages + + if (tools === request.tools && system === request.system && messages === request.messages) return request + return LLMRequest.update(request, { tools, system, messages }) +} diff --git a/packages/llm/src/route/client.ts b/packages/llm/src/route/client.ts index 734eedff2..2d9de2fd3 100644 --- a/packages/llm/src/route/client.ts +++ b/packages/llm/src/route/client.ts @@ -8,6 +8,7 @@ import type { Transport, TransportRuntime } from "./transport" import { WebSocketExecutor } from "./transport" import type { Service as WebSocketExecutorService } from "./transport/websocket" import type { Protocol } from "./protocol" +import { applyCachePolicy } from "../cache-policy" import * as ProviderShared from "../protocols/shared" import * as ToolRuntime from "../tool-runtime" import type { Tools } from "../tool" @@ -400,7 +401,7 @@ export function make( // validated provider body plus transport-private prepared data, but does not // execute transport. const compile = Effect.fn("LLM.compile")(function* (request: LLMRequest) { - const resolved = resolveRequestOptions(request) + const resolved = applyCachePolicy(resolveRequestOptions(request)) const route = registeredRoute(resolved.model.route) if (!route) return yield* noRoute(resolved.model) diff --git a/packages/llm/src/schema/messages.ts b/packages/llm/src/schema/messages.ts index cc6b89a2c..c38a66d33 100644 --- a/packages/llm/src/schema/messages.ts +++ b/packages/llm/src/schema/messages.ts @@ -1,6 +1,6 @@ import { Schema } from "effect" import { JsonSchema, MessageRole, ProviderMetadata } from "./ids" -import { CacheHint, GenerationOptions, HttpOptions, ModelRef, ProviderOptions } from "./options" +import { CacheHint, CachePolicy, GenerationOptions, HttpOptions, ModelRef, ProviderOptions } from "./options" const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value) @@ -206,6 +206,7 @@ export class LLMRequest extends Schema.Class("LLM.Request")({ providerOptions: Schema.optional(ProviderOptions), http: Schema.optional(HttpOptions), responseFormat: Schema.optional(ResponseFormat), + cache: Schema.optional(CachePolicy), metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), }) {} @@ -223,6 +224,7 @@ export namespace LLMRequest { providerOptions: request.providerOptions, http: request.http, responseFormat: request.responseFormat, + cache: request.cache, metadata: request.metadata, }) diff --git a/packages/llm/src/schema/options.ts b/packages/llm/src/schema/options.ts index 9a618aa8a..cead47ee4 100644 --- a/packages/llm/src/schema/options.ts +++ b/packages/llm/src/schema/options.ts @@ -200,3 +200,35 @@ export class CacheHint extends Schema.Class("LLM.CacheHint")({ type: Schema.Literals(["ephemeral", "persistent"]), ttlSeconds: Schema.optional(Schema.Number), }) {} + +// Auto-placement policy for prompt caching. The protocol-neutral lowering step +// reads this and injects `CacheHint`s at the configured boundaries; the +// per-protocol body builders then translate those hints into wire markers as +// usual. `"auto"` is the recommended default for agent loops — it places one +// breakpoint at the last tool definition, one at the last system part, and one +// at the latest user message. The combination of provider invalidation +// hierarchy (tools → system → messages) and Anthropic/Bedrock's 20-block +// lookback means three trailing breakpoints reliably cover the static prefix. +// +// Pass `"none"` to opt out entirely (the legacy behavior). Pass the granular +// object form to override individual choices. +export const CachePolicyObject = Schema.Struct({ + tools: Schema.optional(Schema.Boolean), + system: Schema.optional(Schema.Boolean), + messages: Schema.optional( + Schema.Union([ + Schema.Literal("latest-user-message"), + Schema.Literal("latest-assistant"), + Schema.Struct({ tail: Schema.Number }), + ]), + ), + ttlSeconds: Schema.optional(Schema.Number), +}) +export type CachePolicyObject = Schema.Schema.Type + +export const CachePolicy = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("none"), + CachePolicyObject, +]) +export type CachePolicy = Schema.Schema.Type diff --git a/packages/llm/test/cache-policy.test.ts b/packages/llm/test/cache-policy.test.ts new file mode 100644 index 000000000..5cc931fbe --- /dev/null +++ b/packages/llm/test/cache-policy.test.ts @@ -0,0 +1,262 @@ +import { describe, expect, test } from "bun:test" +import { Effect } from "effect" +import { CacheHint, LLM } from "../src" +import { LLMClient } from "../src/route" +import * as AnthropicMessages from "../src/protocols/anthropic-messages" +import * as BedrockConverse from "../src/protocols/bedrock-converse" +import * as Gemini from "../src/protocols/gemini" +import * as OpenAIChat from "../src/protocols/openai-chat" +import { applyCachePolicy } from "../src/cache-policy" +import { it } from "./lib/effect" + +const anthropicModel = AnthropicMessages.model({ + id: "claude-sonnet-4-5", + baseURL: "https://api.anthropic.test/v1/", + headers: { "x-api-key": "test" }, +}) + +const bedrockModel = BedrockConverse.model({ + id: "anthropic.claude-3-5-sonnet-20241022-v2:0", + credentials: { region: "us-east-1", accessKeyId: "fixture", secretAccessKey: "fixture" }, +}) + +const openaiModel = OpenAIChat.model({ + id: "gpt-4o-mini", + baseURL: "https://api.openai.test/v1/", + headers: { authorization: "Bearer test" }, +}) + +const geminiModel = Gemini.model({ + id: "gemini-2.5-flash", + baseURL: "https://generativelanguage.test/v1beta/", + headers: { "x-goog-api-key": "test" }, +}) + +describe("applyCachePolicy", () => { + it.effect("undefined cache leaves the request untouched (opt-in default)", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: anthropicModel, + system: "You are concise.", + prompt: "hi", + }), + ) + + expect(prepared.body).toMatchObject({ + system: [{ type: "text", text: "You are concise.", cache_control: undefined }], + }) + }), + ) + + it.effect("'auto' marks the last tool, last system part, and latest user message on Anthropic", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: anthropicModel, + system: "Sys A", + tools: [{ name: "t1", description: "t1", inputSchema: { type: "object", properties: {} } }], + messages: [ + LLM.user("first user"), + LLM.assistant("assistant reply"), + LLM.user("latest user message"), + ], + cache: "auto", + }), + ) + + expect(prepared.body).toMatchObject({ + tools: [{ name: "t1", cache_control: { type: "ephemeral" } }], + system: [{ type: "text", text: "Sys A", cache_control: { type: "ephemeral" } }], + messages: [ + { role: "user", content: [{ type: "text", text: "first user" }] }, + { role: "assistant", content: [{ type: "text", text: "assistant reply" }] }, + { + role: "user", + content: [{ type: "text", text: "latest user message", cache_control: { type: "ephemeral" } }], + }, + ], + }) + }), + ) + + it.effect("'auto' is a no-op on OpenAI (implicit caching protocol)", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: openaiModel, + system: "Sys", + prompt: "hi", + cache: "auto", + }), + ) + + const body = prepared.body as { messages: Array<{ content: unknown }> } + // OpenAI doesn't accept cache_control on messages — policy must skip. + const flat = JSON.stringify(body) + expect(flat).not.toContain("cache_control") + expect(flat).not.toContain("cachePoint") + }), + ) + + it.effect("'auto' is a no-op on Gemini (out-of-band caching protocol)", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: geminiModel, + system: "Sys", + prompt: "hi", + cache: "auto", + }), + ) + + const flat = JSON.stringify(prepared.body) + expect(flat).not.toContain("cache_control") + expect(flat).not.toContain("cachePoint") + }), + ) + + it.effect("'auto' on Bedrock emits cachePoint markers in the right places", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: bedrockModel, + system: "Sys", + tools: [{ name: "t1", description: "t1", inputSchema: { type: "object", properties: {} } }], + messages: [LLM.user("first user"), LLM.assistant("reply"), LLM.user("latest user")], + cache: "auto", + }), + ) + + expect(prepared.body).toMatchObject({ + toolConfig: { + tools: [{ toolSpec: { name: "t1" } }, { cachePoint: { type: "default" } }], + }, + system: [{ text: "Sys" }, { cachePoint: { type: "default" } }], + messages: [ + { role: "user", content: [{ text: "first user" }] }, + { role: "assistant", content: [{ text: "reply" }] }, + { role: "user", content: [{ text: "latest user" }, { cachePoint: { type: "default" } }] }, + ], + }) + }), + ) + + it.effect("'none' disables auto placement even when manual hints exist", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: anthropicModel, + system: "Sys", + tools: [{ name: "t1", description: "t1", inputSchema: { type: "object", properties: {} } }], + prompt: "hi", + cache: "none", + }), + ) + + expect(prepared.body).toMatchObject({ + tools: [{ name: "t1", cache_control: undefined }], + system: [{ type: "text", text: "Sys", cache_control: undefined }], + }) + }), + ) + + it.effect("granular object form: tools-only marks just tools", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: anthropicModel, + system: "Sys", + tools: [{ name: "t1", description: "t1", inputSchema: { type: "object", properties: {} } }], + prompt: "hi", + cache: { tools: true }, + }), + ) + + expect(prepared.body).toMatchObject({ + tools: [{ name: "t1", cache_control: { type: "ephemeral" } }], + system: [{ type: "text", text: "Sys", cache_control: undefined }], + }) + }), + ) + + it.effect("auto policy preserves manual CacheHints on other parts", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: anthropicModel, + system: [ + { type: "text", text: "first system", cache: new CacheHint({ type: "ephemeral", ttlSeconds: 3600 }) }, + { type: "text", text: "last system" }, + ], + prompt: "hi", + cache: "auto", + }), + ) + + const body = prepared.body as { system: Array<{ text: string; cache_control?: unknown }> } + expect(body.system[0]?.cache_control).toEqual({ type: "ephemeral", ttl: "1h" }) + expect(body.system[1]?.cache_control).toEqual({ type: "ephemeral" }) + }), + ) + + it.effect("ttlSeconds in the policy flows through to wire markers", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: anthropicModel, + system: "Sys", + prompt: "hi", + cache: { system: true, ttlSeconds: 3600 }, + }), + ) + + expect(prepared.body).toMatchObject({ + system: [{ type: "text", text: "Sys", cache_control: { type: "ephemeral", ttl: "1h" } }], + }) + }), + ) + + it.effect("messages: { tail: 2 } marks the last 2 message boundaries", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: anthropicModel, + messages: [LLM.user("u1"), LLM.assistant("a1"), LLM.user("u2"), LLM.assistant("a2")], + cache: { messages: { tail: 2 } }, + }), + ) + + const body = prepared.body as { messages: Array<{ content: Array<{ cache_control?: unknown }> }> } + expect(body.messages[0]?.content[0]?.cache_control).toBeUndefined() + expect(body.messages[1]?.content[0]?.cache_control).toBeUndefined() + expect(body.messages[2]?.content[0]?.cache_control).toEqual({ type: "ephemeral" }) + expect(body.messages[3]?.content[0]?.cache_control).toEqual({ type: "ephemeral" }) + }), + ) + + it.effect("'latest-assistant' marks the last assistant message", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: anthropicModel, + messages: [LLM.user("u1"), LLM.assistant("a1"), LLM.user("u2")], + cache: { messages: "latest-assistant" }, + }), + ) + + const body = prepared.body as { messages: Array<{ content: Array<{ cache_control?: unknown }> }> } + expect(body.messages[0]?.content[0]?.cache_control).toBeUndefined() + expect(body.messages[1]?.content[0]?.cache_control).toEqual({ type: "ephemeral" }) + expect(body.messages[2]?.content[0]?.cache_control).toBeUndefined() + }), + ) + + test("returns the same request reference when policy is a no-op (pure function)", () => { + const request = LLM.request({ + model: anthropicModel, + prompt: "hi", + }) + expect(applyCachePolicy(request)).toBe(request) + }) +}) diff --git a/packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json b/packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json new file mode 100644 index 000000000..697faea28 --- /dev/null +++ b/packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json @@ -0,0 +1,53 @@ +{ + "version": 1, + "metadata": { + "name": "anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call", + "recordedAt": "2026-05-11T01:52:54.319Z", + "tags": [ + "prefix:anthropic-messages-cache", + "provider:anthropic", + "protocol:anthropic-messages", + "cache" + ] + }, + "interactions": [ + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.anthropic.com/v1/messages", + "headers": { + "anthropic-version": "2023-06-01", + "content-type": "application/json" + }, + "body": "{\"model\":\"claude-haiku-4-5-20251001\",\"system\":[{\"type\":\"text\",\"text\":\"You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. \",\"cache_control\":{\"type\":\"ephemeral\"}}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Say hi.\"}]}],\"stream\":true,\"max_tokens\":16,\"temperature\":0}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_01NSbhSJdF1R6Uz81RRKxd55\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":9,\"cache_creation_input_tokens\":5752,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":5752,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":2,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hi.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":9,\"cache_creation_input_tokens\":5752,\"cache_read_input_tokens\":0,\"output_tokens\":5} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n" + } + }, + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.anthropic.com/v1/messages", + "headers": { + "anthropic-version": "2023-06-01", + "content-type": "application/json" + }, + "body": "{\"model\":\"claude-haiku-4-5-20251001\",\"system\":[{\"type\":\"text\",\"text\":\"You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. \",\"cache_control\":{\"type\":\"ephemeral\"}}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Say hi.\"}]}],\"stream\":true,\"max_tokens\":16,\"temperature\":0}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_01W9dNB2vnT7HoPQmDfKyniu\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":9,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":5752,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":2,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hi.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":9,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":5752,\"output_tokens\":5} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n" + } + } + ] +} diff --git a/packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json b/packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json new file mode 100644 index 000000000..55c7c6c32 --- /dev/null +++ b/packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "metadata": { + "name": "gemini-cache/reports-cachedcontenttokencount-on-identical-second-call", + "recordedAt": "2026-05-11T01:55:40.600Z", + "tags": [ + "prefix:gemini-cache", + "provider:google", + "protocol:gemini", + "cache" + ] + }, + "interactions": [ + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse", + "headers": { + "content-type": "application/json" + }, + "body": "{\"contents\":[{\"role\":\"user\",\"parts\":[{\"text\":\"Say hi.\"}]}],\"systemInstruction\":{\"parts\":[{\"text\":\"You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. \"}]},\"generationConfig\":{\"maxOutputTokens\":16,\"temperature\":0}}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream" + }, + "body": "" + } + }, + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse", + "headers": { + "content-type": "application/json" + }, + "body": "{\"contents\":[{\"role\":\"user\",\"parts\":[{\"text\":\"Say hi.\"}]}],\"systemInstruction\":{\"parts\":[{\"text\":\"You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. \"}]},\"generationConfig\":{\"maxOutputTokens\":16,\"temperature\":0}}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream" + }, + "body": "" + } + } + ] +} diff --git a/packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json b/packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json new file mode 100644 index 000000000..a9e7acbba --- /dev/null +++ b/packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "metadata": { + "name": "openai-responses-cache/reports-cached-tokens-on-identical-second-call", + "recordedAt": "2026-05-11T01:41:58.951Z", + "tags": [ + "prefix:openai-responses-cache", + "provider:openai", + "protocol:openai-responses", + "cache" + ] + }, + "interactions": [ + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.openai.com/v1/responses", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"gpt-4.1-mini\",\"input\":[{\"role\":\"system\",\"content\":\"You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. \"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Say hi.\"}]}],\"prompt_cache_key\":\"recorded-cache-test\",\"max_output_tokens\":16,\"temperature\":0,\"stream\":true}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_00b4acfe385b75d6006a0133e252e4819faecb37d96affd4bf\",\"object\":\"response\",\"created_at\":1778463714,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":16,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"recorded-cache-test\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":true,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}\n\nevent: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_00b4acfe385b75d6006a0133e252e4819faecb37d96affd4bf\",\"object\":\"response\",\"created_at\":1778463714,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":16,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"recorded-cache-test\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":true,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_00b4acfe385b75d6006a0133e42ad8819f83824a88e1160e09\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}\n\nevent: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_00b4acfe385b75d6006a0133e42ad8819f83824a88e1160e09\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"Hi\",\"item_id\":\"msg_00b4acfe385b75d6006a0133e42ad8819f83824a88e1160e09\",\"logprobs\":[],\"obfuscation\":\"NSLkknb2f6J7MB\",\"output_index\":0,\"sequence_number\":4}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\".\",\"item_id\":\"msg_00b4acfe385b75d6006a0133e42ad8819f83824a88e1160e09\",\"logprobs\":[],\"obfuscation\":\"ywmEAhs1uKOLkln\",\"output_index\":0,\"sequence_number\":5}\n\nevent: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_00b4acfe385b75d6006a0133e42ad8819f83824a88e1160e09\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":6,\"text\":\"Hi.\"}\n\nevent: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_00b4acfe385b75d6006a0133e42ad8819f83824a88e1160e09\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Hi.\"},\"sequence_number\":7}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_00b4acfe385b75d6006a0133e42ad8819f83824a88e1160e09\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Hi.\"}],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":8}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_00b4acfe385b75d6006a0133e252e4819faecb37d96affd4bf\",\"object\":\"response\",\"created_at\":1778463714,\"status\":\"completed\",\"background\":false,\"completed_at\":1778463716,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":16,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[{\"id\":\"msg_00b4acfe385b75d6006a0133e42ad8819f83824a88e1160e09\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Hi.\"}],\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"recorded-cache-test\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":true,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":4765,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":3,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":4768},\"user\":null,\"metadata\":{}},\"sequence_number\":9}\n\n" + } + }, + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.openai.com/v1/responses", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"gpt-4.1-mini\",\"input\":[{\"role\":\"system\",\"content\":\"You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. \"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Say hi.\"}]}],\"prompt_cache_key\":\"recorded-cache-test\",\"max_output_tokens\":16,\"temperature\":0,\"stream\":true}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_06a66d5dbf005c28006a0133e48a28819d957163a92a5a56cc\",\"object\":\"response\",\"created_at\":1778463716,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":16,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"recorded-cache-test\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":true,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}\n\nevent: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_06a66d5dbf005c28006a0133e48a28819d957163a92a5a56cc\",\"object\":\"response\",\"created_at\":1778463716,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":16,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"recorded-cache-test\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":true,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_06a66d5dbf005c28006a0133e6a2b0819d90b31eabe0bb0568\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}\n\nevent: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_06a66d5dbf005c28006a0133e6a2b0819d90b31eabe0bb0568\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"Hi\",\"item_id\":\"msg_06a66d5dbf005c28006a0133e6a2b0819d90b31eabe0bb0568\",\"logprobs\":[],\"obfuscation\":\"qLgi78ygFGnuw7\",\"output_index\":0,\"sequence_number\":4}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\".\",\"item_id\":\"msg_06a66d5dbf005c28006a0133e6a2b0819d90b31eabe0bb0568\",\"logprobs\":[],\"obfuscation\":\"dyQaYugaXCUfkYH\",\"output_index\":0,\"sequence_number\":5}\n\nevent: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_06a66d5dbf005c28006a0133e6a2b0819d90b31eabe0bb0568\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":6,\"text\":\"Hi.\"}\n\nevent: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_06a66d5dbf005c28006a0133e6a2b0819d90b31eabe0bb0568\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Hi.\"},\"sequence_number\":7}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_06a66d5dbf005c28006a0133e6a2b0819d90b31eabe0bb0568\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Hi.\"}],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":8}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_06a66d5dbf005c28006a0133e48a28819d957163a92a5a56cc\",\"object\":\"response\",\"created_at\":1778463716,\"status\":\"completed\",\"background\":false,\"completed_at\":1778463718,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":16,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[{\"id\":\"msg_06a66d5dbf005c28006a0133e6a2b0819d90b31eabe0bb0568\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Hi.\"}],\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"recorded-cache-test\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":true,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":4765,\"input_tokens_details\":{\"cached_tokens\":4608},\"output_tokens\":3,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":4768},\"user\":null,\"metadata\":{}},\"sequence_number\":9}\n\n" + } + } + ] +} diff --git a/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts b/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts index b048d53ba..cee31de19 100644 --- a/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts +++ b/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts @@ -28,7 +28,12 @@ const recorded = recordedTests({ provider: "anthropic", protocol: "anthropic-messages", requires: ["ANTHROPIC_API_KEY"], - options: { redactor: Redactor.defaults({ requestHeaders: { allow: ["content-type", "anthropic-version"] } }) }, + // Two identical requests in one cassette — match by recording order so the + // second call replays the cached-hit interaction. + options: { + dispatch: "sequential", + redactor: Redactor.defaults({ requestHeaders: { allow: ["content-type", "anthropic-version"] } }), + }, }) describe("Anthropic Messages cache recorded", () => { diff --git a/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts b/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts index 23dd697b9..400e38849 100644 --- a/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts +++ b/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts @@ -35,6 +35,9 @@ const recorded = recordedTests({ provider: "amazon-bedrock", protocol: "bedrock-converse", requires: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"], + // Two identical requests in one cassette — match by recording order so the + // second call replays the cached-hit interaction. + options: { dispatch: "sequential" }, }) describe("Bedrock Converse cache recorded", () => { diff --git a/packages/llm/test/provider/gemini-cache.recorded.test.ts b/packages/llm/test/provider/gemini-cache.recorded.test.ts index 145728fdc..c3b3e55b3 100644 --- a/packages/llm/test/provider/gemini-cache.recorded.test.ts +++ b/packages/llm/test/provider/gemini-cache.recorded.test.ts @@ -8,7 +8,7 @@ import { recordedTests } from "../recorded-test" const model = Gemini.model({ id: "gemini-2.5-flash", - apiKey: process.env.GEMINI_API_KEY ?? "fixture", + apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? process.env.GEMINI_API_KEY ?? "fixture", }) // Gemini does implicit prefix caching on 2.5+ models above ~1024 tokens. The @@ -28,7 +28,10 @@ const recorded = recordedTests({ prefix: "gemini-cache", provider: "google", protocol: "gemini", - requires: ["GEMINI_API_KEY"], + requires: ["GOOGLE_GENERATIVE_AI_API_KEY"], + // Two identical requests in one cassette — match by recording order so the + // second call replays the cached-hit interaction. + options: { dispatch: "sequential" }, }) describe("Gemini cache recorded", () => { diff --git a/packages/llm/test/provider/openai-responses-cache.recorded.test.ts b/packages/llm/test/provider/openai-responses-cache.recorded.test.ts index 0ac3dfe2b..5a38898c0 100644 --- a/packages/llm/test/provider/openai-responses-cache.recorded.test.ts +++ b/packages/llm/test/provider/openai-responses-cache.recorded.test.ts @@ -29,6 +29,9 @@ const recorded = recordedTests({ provider: "openai", protocol: "openai-responses", requires: ["OPENAI_API_KEY"], + // Two identical requests in one cassette — match by recording order so the + // second call replays the cached-hit interaction, not the cold-miss one. + options: { dispatch: "sequential" }, }) describe("OpenAI Responses cache recorded", () => { From 02cb7e7b71c560fb29d36f0bd56149ab6a5fa3ee Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 02:11:07 +0000 Subject: [PATCH 002/378] chore: generate --- packages/llm/README.md | 12 ++++++------ packages/llm/src/cache-policy.ts | 15 +++------------ packages/llm/src/schema/options.ts | 6 +----- packages/llm/test/cache-policy.test.ts | 6 +----- ...ds-cache-control-on-identical-second-call.json | 7 +------ ...ontenttokencount-on-identical-second-call.json | 7 +------ ...ts-cached-tokens-on-identical-second-call.json | 7 +------ 7 files changed, 14 insertions(+), 46 deletions(-) diff --git a/packages/llm/README.md b/packages/llm/README.md index 434ce369f..e164c4bf5 100644 --- a/packages/llm/README.md +++ b/packages/llm/README.md @@ -82,12 +82,12 @@ LLM.request({ ### Provider behavior table -| Protocol | `cache: "auto"` | -|---|---| -| Anthropic Messages | emits up to 3 `cache_control` markers (4-breakpoint cap enforced) | -| Bedrock Converse | emits up to 3 `cachePoint` blocks (4-breakpoint cap enforced) | -| OpenAI Chat / Responses | no-op (implicit caching above 1024 tokens) | -| Gemini | no-op (implicit caching on 2.5+; explicit `CachedContent` is out-of-band) | +| Protocol | `cache: "auto"` | +| ----------------------- | ------------------------------------------------------------------------- | +| Anthropic Messages | emits up to 3 `cache_control` markers (4-breakpoint cap enforced) | +| Bedrock Converse | emits up to 3 `cachePoint` blocks (4-breakpoint cap enforced) | +| OpenAI Chat / Responses | no-op (implicit caching above 1024 tokens) | +| Gemini | no-op (implicit caching on 2.5+; explicit `CachedContent` is out-of-band) | Normalized cache usage is read back into `response.usage.cacheReadInputTokens` and `cacheWriteInputTokens` across every provider. diff --git a/packages/llm/src/cache-policy.ts b/packages/llm/src/cache-policy.ts index 46d924ab5..b9dca4e88 100644 --- a/packages/llm/src/cache-policy.ts +++ b/packages/llm/src/cache-policy.ts @@ -44,10 +44,7 @@ const RESPECTS_INLINE_HINTS = new Set(["anthropic-messages", "bedrock-converse"] const makeHint = (ttlSeconds: number | undefined): CacheHint => ttlSeconds !== undefined ? new CacheHint({ type: "ephemeral", ttlSeconds }) : new CacheHint({ type: "ephemeral" }) -const markLastTool = ( - tools: ReadonlyArray, - hint: CacheHint, -): ReadonlyArray => { +const markLastTool = (tools: ReadonlyArray, hint: CacheHint): ReadonlyArray => { if (tools.length === 0) return tools const last = tools.length - 1 if (tools[last]!.cache) return tools @@ -67,11 +64,7 @@ const lastIndexOfRole = (messages: ReadonlyArray, role: Message["role"] // Mark the last text part of `messages[index]`. If no text part exists, mark // the last content part regardless of type — that's the breakpoint position // in tool-result-only messages too. -const markMessageAt = ( - messages: ReadonlyArray, - index: number, - hint: CacheHint, -): ReadonlyArray => { +const markMessageAt = (messages: ReadonlyArray, index: number, hint: CacheHint): ReadonlyArray => { if (index < 0 || index >= messages.length) return messages const target = messages[index]! if (target.content.length === 0) return messages @@ -79,9 +72,7 @@ const markMessageAt = ( const markAt = lastTextIndex >= 0 ? lastTextIndex : target.content.length - 1 const existing = target.content[markAt]! if ("cache" in existing && existing.cache) return messages - const nextContent = target.content.map((part, i) => - i === markAt ? ({ ...part, cache: hint } as ContentPart) : part, - ) + const nextContent = target.content.map((part, i) => (i === markAt ? ({ ...part, cache: hint } as ContentPart) : part)) const next = new Message({ ...target, content: nextContent }) // Single pass over `messages`, substituting the one updated entry. Long // conversations call this on every request, so avoid `.map()` here — its diff --git a/packages/llm/src/schema/options.ts b/packages/llm/src/schema/options.ts index cead47ee4..0f40196f7 100644 --- a/packages/llm/src/schema/options.ts +++ b/packages/llm/src/schema/options.ts @@ -226,9 +226,5 @@ export const CachePolicyObject = Schema.Struct({ }) export type CachePolicyObject = Schema.Schema.Type -export const CachePolicy = Schema.Union([ - Schema.Literal("auto"), - Schema.Literal("none"), - CachePolicyObject, -]) +export const CachePolicy = Schema.Union([Schema.Literal("auto"), Schema.Literal("none"), CachePolicyObject]) export type CachePolicy = Schema.Schema.Type diff --git a/packages/llm/test/cache-policy.test.ts b/packages/llm/test/cache-policy.test.ts index 5cc931fbe..640556105 100644 --- a/packages/llm/test/cache-policy.test.ts +++ b/packages/llm/test/cache-policy.test.ts @@ -56,11 +56,7 @@ describe("applyCachePolicy", () => { model: anthropicModel, system: "Sys A", tools: [{ name: "t1", description: "t1", inputSchema: { type: "object", properties: {} } }], - messages: [ - LLM.user("first user"), - LLM.assistant("assistant reply"), - LLM.user("latest user message"), - ], + messages: [LLM.user("first user"), LLM.assistant("assistant reply"), LLM.user("latest user message")], cache: "auto", }), ) diff --git a/packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json b/packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json index 697faea28..8cf2be05c 100644 --- a/packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json +++ b/packages/llm/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json @@ -3,12 +3,7 @@ "metadata": { "name": "anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call", "recordedAt": "2026-05-11T01:52:54.319Z", - "tags": [ - "prefix:anthropic-messages-cache", - "provider:anthropic", - "protocol:anthropic-messages", - "cache" - ] + "tags": ["prefix:anthropic-messages-cache", "provider:anthropic", "protocol:anthropic-messages", "cache"] }, "interactions": [ { diff --git a/packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json b/packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json index 55c7c6c32..014575688 100644 --- a/packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json +++ b/packages/llm/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json @@ -3,12 +3,7 @@ "metadata": { "name": "gemini-cache/reports-cachedcontenttokencount-on-identical-second-call", "recordedAt": "2026-05-11T01:55:40.600Z", - "tags": [ - "prefix:gemini-cache", - "provider:google", - "protocol:gemini", - "cache" - ] + "tags": ["prefix:gemini-cache", "provider:google", "protocol:gemini", "cache"] }, "interactions": [ { diff --git a/packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json b/packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json index a9e7acbba..25b561197 100644 --- a/packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json +++ b/packages/llm/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json @@ -3,12 +3,7 @@ "metadata": { "name": "openai-responses-cache/reports-cached-tokens-on-identical-second-call", "recordedAt": "2026-05-11T01:41:58.951Z", - "tags": [ - "prefix:openai-responses-cache", - "provider:openai", - "protocol:openai-responses", - "cache" - ] + "tags": ["prefix:openai-responses-cache", "provider:openai", "protocol:openai-responses", "cache"] }, "interactions": [ { From cfbf5d1c6f866d5673a6b2940211d05feaf717ad Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 02:20:35 +0000 Subject: [PATCH 003/378] 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 558264474..4244e0c0e 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-LTo0ohJN5hBOubqFLVL45unVEIwBDkACNVv64k2nkq4=", - "aarch64-linux": "sha256-oYKY2UJRWG2fhufW4aGujX/Poou93023ZF2Fu7oyYOw=", - "aarch64-darwin": "sha256-618c9vqKN5I+no1nzylctAiWvqw7Bsa+bzSTNwXmSQA=", - "x86_64-darwin": "sha256-1ro3/gH0FC0TWXwWT+k675xR396GE98HpnBEeuD4t6k=" + "x86_64-linux": "sha256-baGxh+hk/rPhg0xI/OdMDz6dPwncgercYNBdTPnLX9o=", + "aarch64-linux": "sha256-VTWKq679B3Q4ZnAoQzC4VSCYA09wWecNJ+JajvjNB1U=", + "aarch64-darwin": "sha256-orf2zIBMTiiQrt/6qCzE+o0oKhv6u8zXF9DH1Bo3lbo=", + "x86_64-darwin": "sha256-1MZC1fadRoY4lhkmjlcUQTLYH9Q8pDI1bxd5f94f1xU=" } } From 721ff5121e56891ee1650715a637364a513c2ab6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 11 May 2026 04:27:46 +0200 Subject: [PATCH 004/378] fix prompt history behaviour and session line up/down commands (#26797) --- .../cli/cmd/tui/component/prompt/index.tsx | 32 +++++++------------ .../src/cli/cmd/tui/routes/session/index.tsx | 4 +-- 2 files changed, 14 insertions(+), 22 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 f1ce2b676..f3217fcba 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -926,13 +926,7 @@ export function Prompt(props: PromptProps) { target: inputTarget, enabled: (() => { cursorVersion() - return ( - inputTarget() !== undefined && - !props.disabled && - !auto()?.visible && - input !== undefined && - (input.cursorOffset === 0 || input.visualCursor.visualRow === 0) - ) + return inputTarget() !== undefined && !props.disabled && !auto()?.visible && input !== undefined })(), commands: [ { @@ -941,12 +935,12 @@ export function Prompt(props: PromptProps) { category: "Prompt", run() { if (input.cursorOffset !== 0) { - input.cursorOffset = 0 - return + if (input.scrollY + input.visualCursor.visualRow === 0) input.cursorOffset = 0 + return false } const item = history.move(-1, input.plainText) - if (!item) return + if (!item) return false input.setText(item.input) setStore("prompt", item) setStore("mode", item.mode ?? "normal") @@ -964,13 +958,7 @@ export function Prompt(props: PromptProps) { target: inputTarget, enabled: (() => { cursorVersion() - return ( - inputTarget() !== undefined && - !props.disabled && - !auto()?.visible && - input !== undefined && - (input.cursorOffset === input.plainText.length || input.visualCursor.visualRow === input.height - 1) - ) + return inputTarget() !== undefined && !props.disabled && !auto()?.visible && input !== undefined })(), commands: [ { @@ -979,12 +967,16 @@ export function Prompt(props: PromptProps) { category: "Prompt", run() { if (input.cursorOffset !== input.plainText.length) { - input.cursorOffset = input.plainText.length - return + if ( + input.scrollY + input.visualCursor.visualRow === + Math.max(0, input.editorView.getTotalVirtualLineCount() - 1) + ) + input.cursorOffset = input.plainText.length + return false } const item = history.move(1, input.plainText) - if (!item) return + if (!item) return false input.setText(item.input) setStore("prompt", item) setStore("mode", item.mode ?? "normal") 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 b0b48ec42..b2ee3af62 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -746,7 +746,7 @@ export function Session() { title: "Line up", value: "session.line.up", category: "Session", - enabled: false, + hidden: true, run: () => { scroll.scrollBy(-1) dialog.clear() @@ -756,7 +756,7 @@ export function Session() { title: "Line down", value: "session.line.down", category: "Session", - enabled: false, + hidden: true, run: () => { scroll.scrollBy(1) dialog.clear() From 9b369ee815a6f5ea6695972d7275d7aabf058b5f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 10 May 2026 22:46:01 -0400 Subject: [PATCH 005/378] chore(llm): make cache: 'auto' the default (#26798) --- packages/llm/README.md | 17 ++++++++--------- packages/llm/src/cache-policy.ts | 12 ++++++------ packages/llm/test/cache-policy.test.ts | 8 ++++++-- .../anthropic-messages-cache.recorded.test.ts | 3 +++ .../test/provider/anthropic-messages.test.ts | 4 ++++ .../bedrock-converse-cache.recorded.test.ts | 3 +++ .../llm/test/provider/bedrock-converse.test.ts | 8 ++++++++ packages/llm/test/recorded-scenarios.ts | 3 +++ 8 files changed, 41 insertions(+), 17 deletions(-) diff --git a/packages/llm/README.md b/packages/llm/README.md index e164c4bf5..321bf715b 100644 --- a/packages/llm/README.md +++ b/packages/llm/README.md @@ -35,26 +35,25 @@ Run `LLMClient.stream(request)` instead of `generate` when you want incremental ## Caching -Prompt caching is unified across providers. Mark content with a `CacheHint` and each protocol translates it to its wire format (`cache_control` on Anthropic, `cachePoint` on Bedrock; OpenAI's implicit caching needs no markers). +Prompt caching is **on by default**. Every `LLMRequest` resolves to `cache: "auto"` unless the caller opts out with `cache: "none"`. Each protocol translates `CacheHint`s to its wire format (`cache_control` on Anthropic, `cachePoint` on Bedrock; OpenAI and Gemini do implicit caching server-side and don't need inline markers — auto is a no-op there). ### Auto placement -The simplest path is `cache: "auto"` on the request: +`"auto"` places three breakpoints — last tool definition, last system part, latest user message. The last-user-message boundary is the load-bearing detail: in a tool-use loop, a single user turn expands into many assistant/tool round-trips, all sharing that prefix. Caching at that boundary lets every intra-turn API call hit. + +The math justifies the default: Anthropic's 5-minute cache write is 1.25× base, read is 0.1×, so a single reuse within 5 minutes already wins. One-shot completions below the per-model minimum-cacheable-token threshold silently no-op on the wire, so the worst case is harmless. + +### Opting out ```ts LLM.request({ model, system, - messages, - tools, - cache: "auto", + prompt: "one-off question", + cache: "none", }) ``` -`"auto"` places three breakpoints — last tool definition, last system part, latest user message. The last-user-message boundary is the load-bearing detail: in a tool-use loop, a single user turn expands into many assistant/tool round-trips, all sharing that prefix. Caching at that boundary lets every intra-turn API call hit. - -On OpenAI and Gemini `"auto"` is a no-op (their wire formats don't accept inline markers — both use implicit caching). On Anthropic and Bedrock it emits provider-native cache markers. - ### Granular policy ```ts diff --git a/packages/llm/src/cache-policy.ts b/packages/llm/src/cache-policy.ts index b9dca4e88..6ab7a049f 100644 --- a/packages/llm/src/cache-policy.ts +++ b/packages/llm/src/cache-policy.ts @@ -24,15 +24,15 @@ const AUTO: CachePolicyObject = { const NONE: CachePolicyObject = {} // Resolution rules: -// - undefined → "none" (opt-in default so the policy never changes wire -// shape for existing callers; downstream code can flip to -// `cache: "auto"` once they audit the placement choices). -// - "auto" → the recommended policy: tools + system + latest user msg. +// - undefined → "auto" — caching is on by default. The math favors it: +// Anthropic 5m-cache write is 1.25x base, read is 0.1x, +// so a single reuse within 5 minutes already wins. +// - "auto" → tools + system + latest user msg. // - "none" → no auto placement; manual `CacheHint`s still flow. // - object form → exactly what the caller asked for. const resolve = (policy: CachePolicy | undefined): CachePolicyObject => { - if (policy === undefined || policy === "none") return NONE - if (policy === "auto") return AUTO + if (policy === undefined || policy === "auto") return AUTO + if (policy === "none") return NONE return policy } diff --git a/packages/llm/test/cache-policy.test.ts b/packages/llm/test/cache-policy.test.ts index 640556105..e742ca5e6 100644 --- a/packages/llm/test/cache-policy.test.ts +++ b/packages/llm/test/cache-policy.test.ts @@ -33,7 +33,7 @@ const geminiModel = Gemini.model({ }) describe("applyCachePolicy", () => { - it.effect("undefined cache leaves the request untouched (opt-in default)", () => + it.effect("undefined cache resolves to 'auto' (the recommended default)", () => Effect.gen(function* () { const prepared = yield* LLMClient.prepare( LLM.request({ @@ -43,8 +43,11 @@ describe("applyCachePolicy", () => { }), ) + // No explicit cache field → auto policy fires → last system part + latest + // user message both get cache_control markers. expect(prepared.body).toMatchObject({ - system: [{ type: "text", text: "You are concise.", cache_control: undefined }], + system: [{ type: "text", text: "You are concise.", cache_control: { type: "ephemeral" } }], + messages: [{ role: "user", content: [{ type: "text", text: "hi", cache_control: { type: "ephemeral" } }] }], }) }), ) @@ -252,6 +255,7 @@ describe("applyCachePolicy", () => { const request = LLM.request({ model: anthropicModel, prompt: "hi", + cache: "none", }) expect(applyCachePolicy(request)).toBe(request) }) diff --git a/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts b/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts index cee31de19..cb144b1a5 100644 --- a/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts +++ b/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts @@ -20,6 +20,9 @@ const cacheRequest = LLM.request({ model, system: [{ type: "text", text: LARGE_CACHEABLE_SYSTEM, cache: new CacheHint({ type: "ephemeral" }) }], prompt: "Say hi.", + // Manual hint on the system part is the only marker we want here — skip the + // auto-policy's latest-user-message breakpoint so the cassette body matches. + cache: "none", generation: { maxTokens: 16, temperature: 0 }, }) diff --git a/packages/llm/test/provider/anthropic-messages.test.ts b/packages/llm/test/provider/anthropic-messages.test.ts index 3be041c94..a867d1659 100644 --- a/packages/llm/test/provider/anthropic-messages.test.ts +++ b/packages/llm/test/provider/anthropic-messages.test.ts @@ -18,6 +18,9 @@ const request = LLM.request({ model, system: { type: "text", text: "You are concise.", cache: new CacheHint({ type: "ephemeral" }) }, prompt: "Say hello.", + // This fixture predates the `cache: "auto"` default; pin the policy off so + // existing wire-shape assertions only see the manual hint on the system part. + cache: "none", generation: { maxTokens: 20, temperature: 0 }, }) @@ -48,6 +51,7 @@ describe("Anthropic Messages route", () => { LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } })]), LLM.toolMessage({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), ], + cache: "none", }), ) diff --git a/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts b/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts index 400e38849..16c44099c 100644 --- a/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts +++ b/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts @@ -27,6 +27,9 @@ const cacheRequest = LLM.request({ model, system: [{ type: "text", text: LARGE_CACHEABLE_SYSTEM, cache: new CacheHint({ type: "ephemeral" }) }], prompt: "Say hi.", + // Manual hint on the system part is the only marker we want here — skip the + // auto-policy's latest-user-message breakpoint so the cassette body matches. + cache: "none", generation: { maxTokens: 16, temperature: 0 }, }) diff --git a/packages/llm/test/provider/bedrock-converse.test.ts b/packages/llm/test/provider/bedrock-converse.test.ts index afadd89ac..208b56527 100644 --- a/packages/llm/test/provider/bedrock-converse.test.ts +++ b/packages/llm/test/provider/bedrock-converse.test.ts @@ -63,6 +63,9 @@ const baseRequest = LLM.request({ model, system: "You are concise.", prompt: "Say hello.", + // Wire-shape assertions in this file predate the `cache: "auto"` default; + // pin the policy off so they only exercise the lowering path itself. + cache: "none", generation: { maxTokens: 64, temperature: 0 }, }) @@ -125,6 +128,7 @@ describe("Bedrock Converse route", () => { LLM.assistant([LLM.toolCall({ id: "tool_1", name: "lookup", input: { query: "weather" } })]), LLM.toolMessage({ id: "tool_1", name: "lookup", result: { forecast: "sunny" } }), ], + cache: "none", }), ) @@ -339,6 +343,7 @@ describe("Bedrock Converse route", () => { { type: "media", mediaType: "image/webp", data: "DDDD" }, ]), ], + cache: "none", }), ) @@ -470,6 +475,7 @@ describe("Bedrock Converse route", () => { LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: {} })]), LLM.toolMessage({ id: "call_1", name: "lookup", result: { temp: 72 }, cache }), ], + cache: "none", }), ) @@ -555,6 +561,7 @@ describe("Bedrock Converse recorded", () => { model: recordedModel(), system: "Reply with the single word 'Hello'.", prompt: "Say hello.", + cache: "none", generation: { maxTokens: 16, temperature: 0 }, }), ) @@ -577,6 +584,7 @@ describe("Bedrock Converse recorded", () => { prompt: "Call get_weather with city exactly Paris.", tools: [weatherTool], toolChoice: LLM.toolChoice(weatherTool), + cache: "none", generation: { maxTokens: 80, temperature: 0 }, }), ) diff --git a/packages/llm/test/recorded-scenarios.ts b/packages/llm/test/recorded-scenarios.ts index 8a02bc3a0..127a444a1 100644 --- a/packages/llm/test/recorded-scenarios.ts +++ b/packages/llm/test/recorded-scenarios.ts @@ -51,6 +51,7 @@ export const textRequest = (input: { model: input.model, system: "You are concise.", prompt: input.prompt ?? "Reply with exactly: Hello!", + cache: "none", generation: input.temperature === false ? { maxTokens: input.maxTokens ?? 20 } @@ -70,6 +71,7 @@ export const weatherToolRequest = (input: { prompt: "Call get_weather with city exactly Paris.", tools: [weatherTool], toolChoice: LLM.toolChoice(weatherTool), + cache: "none", generation: input.temperature === false ? { maxTokens: input.maxTokens ?? 80 } @@ -88,6 +90,7 @@ export const weatherToolLoopRequest = (input: { model: input.model, system: input.system ?? "Use the get_weather tool, then answer in one short sentence.", prompt: "What is the weather in Paris?", + cache: "none", generation: input.temperature === false ? { maxTokens: input.maxTokens ?? 80 } From 274033cd52464c5fe8eadde8e2b7fdd516b4549f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 10 May 2026 22:59:20 -0400 Subject: [PATCH 006/378] Validate prompt messages with Effect Schema (#26796) --- packages/opencode/src/session/message-v2.ts | 33 +++++---------------- packages/opencode/src/session/prompt.ts | 15 ++++++---- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 3eb6f07b8..a8c8dabc8 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -23,7 +23,7 @@ import type { SystemError } from "bun" import type { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "@/provider/schema" import { Effect, Schema, Types } from "effect" -import { zod, ZodOverride } from "@opencode-ai/core/effect-zod" +import { zod } from "@opencode-ai/core/effect-zod" import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema" import { namedSchemaError } from "@/util/named-schema-error" import * as EffectLogger from "@opencode-ai/core/effect/logger" @@ -402,7 +402,7 @@ export const User = Schema.Struct({ .pipe(withStatics((s) => ({ zod: zod(s) }))) export type User = Types.DeepMutable> -const _Part = Schema.Union([ +export const Part = Schema.Union([ TextPart, SubtaskPart, ReasoningPart, @@ -416,22 +416,6 @@ const _Part = Schema.Union([ RetryPart, CompactionPart, ]).annotate({ discriminator: "type", identifier: "Part" }) -export const Part = Object.assign(_Part, { - zod: zod(_Part) as unknown as z.ZodType< - | TextPart - | SubtaskPart - | ReasoningPart - | FilePart - | ToolPart - | StepStartPart - | StepFinishPart - | SnapshotPart - | PatchPart - | AgentPart - | RetryPart - | CompactionPart - >, -}) export type Part = | TextPart | SubtaskPart @@ -573,15 +557,12 @@ export type Assistant = Omit, -}) +export const Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "Message" }) export type Info = User | Assistant const UpdatedEventSchema = Schema.Struct({ sessionID: SessionID, - info: _Info, + info: Info, }) const RemovedEventSchema = Schema.Struct({ @@ -591,7 +572,7 @@ const RemovedEventSchema = Schema.Struct({ const PartUpdatedEventSchema = Schema.Struct({ sessionID: SessionID, - part: _Part, + part: Part, time: NonNegativeInt, }) @@ -639,8 +620,8 @@ export const Event = { } export const WithParts = Schema.Struct({ - info: _Info, - parts: Schema.Array(_Part), + info: Info, + parts: Schema.Array(Part), }).pipe(withStatics((s) => ({ zod: zod(s) }))) export type WithParts = { info: Info diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5414eba2e..7f4f60855 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -65,6 +65,9 @@ import { SessionTable } from "./session.sql" // @ts-ignore globalThis.AI_SDK_LOG_WARNINGS = false +const decodeMessageInfo = Schema.decodeUnknownExit(MessageV2.Info) +const decodeMessagePart = Schema.decodeUnknownExit(MessageV2.Part) + const STRUCTURED_OUTPUT_DESCRIPTION = `Use this tool to return your final response in the requested structured format. IMPORTANT: @@ -1292,26 +1295,26 @@ NOTE: At any point in time through this workflow you should feel free to ask the const parts = resolvedParts - const parsed = MessageV2.Info.zod.safeParse(info) - if (!parsed.success) { + const parsed = decodeMessageInfo(info, { errors: "all", propertyOrder: "original" }) + if (Exit.isFailure(parsed)) { log.error("invalid user message before save", { sessionID: input.sessionID, messageID: info.id, agent: info.agent, model: info.model, - issues: parsed.error.issues, + cause: Cause.pretty(parsed.cause), }) } parts.forEach((part, index) => { - const p = MessageV2.Part.zod.safeParse(part) - if (p.success) return + const p = decodeMessagePart(part, { errors: "all", propertyOrder: "original" }) + if (Exit.isSuccess(p)) return log.error("invalid user part before save", { sessionID: input.sessionID, messageID: info.id, partID: part.id, partType: part.type, index, - issues: p.error.issues, + cause: Cause.pretty(p.cause), part, }) }) From 7235c9c9b81f630cde1e8e7c381ad83a84fa82b2 Mon Sep 17 00:00:00 2001 From: Dax Date: Sun, 10 May 2026 23:23:01 -0400 Subject: [PATCH 007/378] Trace data migrations (#26809) --- packages/opencode/src/data-migration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/data-migration.ts b/packages/opencode/src/data-migration.ts index c3e5a9d2b..0a2973de5 100644 --- a/packages/opencode/src/data-migration.ts +++ b/packages/opencode/src/data-migration.ts @@ -36,7 +36,7 @@ export const layer = Layer.effect( if (completed) continue log.info("running data migration", { name: migration.name }) - yield* migration.run + yield* migration.run.pipe(Effect.withSpan("DataMigration", { attributes: { name: migration.name } })) Database.use((db) => db .insert(DataMigrationTable) From 518264fcd9aa7f9ac0a67fc73a47a140520c2cae Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 11 May 2026 00:26:36 -0500 Subject: [PATCH 008/378] fix(opencode): fix full session fork (#26811) --- .../opencode/src/project/instance-store.ts | 6 ++--- .../routes/instance/httpapi/groups/session.ts | 4 ++-- .../instance/httpapi/handlers/session.ts | 23 ++++++++++++++++--- .../test/server/httpapi-session.test.ts | 1 - 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/project/instance-store.ts b/packages/opencode/src/project/instance-store.ts index 4fa1c3dff..9707305f9 100644 --- a/packages/opencode/src/project/instance-store.ts +++ b/packages/opencode/src/project/instance-store.ts @@ -86,7 +86,7 @@ export const layer: Layer.Layer runDisposers(ctx.directory)) yield* emitDisposed({ directory: ctx.directory, project: ctx.project.id }) }) @@ -109,7 +109,7 @@ export const layer: Layer.Layer() } cache.set(directory, entry) yield* Effect.gen(function* () { - yield* Effect.logInfo("creating instance", { directory }) + yield* Effect.logInfo("creating instance").pipe(Effect.annotateLogs("directory", directory)) yield* completeLoad(directory, input, entry) }).pipe(Effect.forkIn(scope, { startImmediately: true })) return yield* restore(Deferred.await(entry.deferred)) @@ -125,7 +125,7 @@ export const layer: Layer.Layer() } cache.set(directory, entry) yield* Effect.gen(function* () { - yield* Effect.logInfo("reloading instance", { directory }) + yield* Effect.logInfo("reloading instance").pipe(Effect.annotateLogs("directory", directory)) if (previous) { yield* Deferred.await(previous.deferred).pipe(Effect.ignore) yield* Effect.promise(() => runDisposers(directory)) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts index ea68e76ca..2053aba3b 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts @@ -236,9 +236,9 @@ export const SessionApi = HttpApi.make("session") HttpApiEndpoint.post("fork", SessionPaths.fork, { params: { sessionID: SessionID }, query: WorkspaceRoutingQuery, - payload: ForkPayload, + payload: Schema.optional(ForkPayload), success: described(Session.Info, "200"), - error: ApiNotFoundError, + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.fork", diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 6aa87ee84..9230a6fe5 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -187,13 +187,30 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", const fork = Effect.fn("SessionHttpApi.fork")(function* (ctx: { params: { sessionID: SessionID } - payload: typeof ForkPayload.Type + payload?: typeof ForkPayload.Type }) { return yield* SessionError.mapStorageNotFound( - session.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID }), + session.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload?.messageID }), ) }) + const forkRaw = Effect.fn("SessionHttpApi.forkRaw")(function* (ctx: { + params: { sessionID: SessionID } + request: HttpServerRequest.HttpServerRequest + }) { + const body = yield* Effect.orDie(ctx.request.text) + if (body.trim().length === 0) return yield* fork({ params: ctx.params }) + + const json = yield* Effect.try({ + try: () => JSON.parse(body) as unknown, + catch: () => new HttpApiError.BadRequest({}), + }) + const payload = yield* Schema.decodeUnknownEffect(ForkPayload)(json).pipe( + Effect.mapError(() => new HttpApiError.BadRequest({})), + ) + return yield* fork({ params: ctx.params, payload }) + }) + const abort = Effect.fn("SessionHttpApi.abort")(function* (ctx: { params: { sessionID: SessionID } }) { yield* promptSvc.cancel(ctx.params.sessionID) return true @@ -373,7 +390,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", .handleRaw("create", createRaw) .handle("remove", remove) .handle("update", update) - .handle("fork", fork) + .handleRaw("fork", forkRaw) .handle("abort", abort) .handle("init", init) .handle("share", share) diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 24c845183..210863e0c 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -356,7 +356,6 @@ describe("session HttpApi", () => { const forked = yield* requestJson(pathFor(SessionPaths.fork, { sessionID: created.id }), { method: "POST", headers, - body: JSON.stringify({}), }) expect(forked.id).not.toBe(created.id) From b1cb71856eda027cced3c93915544d2d1faea2e9 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 05:27:40 +0000 Subject: [PATCH 009/378] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 4 ++++ packages/sdk/openapi.json | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 6b0f4c6f8..5bba8efc0 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -5590,6 +5590,10 @@ export type SessionForkData = { } export type SessionForkErrors = { + /** + * Bad request + */ + 400: BadRequestError /** * NotFoundError */ diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 40b33147b..fb29d68ed 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -5536,6 +5536,16 @@ } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, "404": { "description": "NotFoundError", "content": { From 5d6f2a1524ba37560fe6cdea5aa684dc196dbc99 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Mon, 11 May 2026 15:15:03 +0800 Subject: [PATCH 010/378] fix(ui): use part_text_accum_delta to prevent markdown cutoff during streaming (#26822) --- .../app/src/context/global-sync/child-store.ts | 1 + .../context/global-sync/event-reducer.test.ts | 1 + .../src/context/global-sync/event-reducer.ts | 17 +++++++++++++++++ .../context/global-sync/session-cache.test.ts | 5 +++++ .../src/context/global-sync/session-cache.ts | 4 ++++ packages/app/src/context/global-sync/types.ts | 3 +++ packages/ui/src/components/message-part.tsx | 5 +++-- packages/ui/src/context/data.tsx | 3 +++ 8 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index 0138310cd..737c6bedc 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -231,6 +231,7 @@ export function createChildStoreManager(input: { limit: 5, message: {}, part: {}, + part_text_accum_delta: {}, }) children[key] = child disposers.set(key, dispose) 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 892129788..f02ac5c7b 100644 --- a/packages/app/src/context/global-sync/event-reducer.test.ts +++ b/packages/app/src/context/global-sync/event-reducer.test.ts @@ -81,6 +81,7 @@ const baseState = (input: Partial = {}) => limit: 10, message: {}, part: {}, + part_text_accum_delta: {}, ...input, }) as State diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index 5f43c341b..13d34ef6c 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -211,6 +211,12 @@ export function applyDirectoryEvent(input: { const result = Binary.search(messages, props.messageID, (m) => m.id) if (result.found) messages.splice(result.index, 1) } + const parts = draft.part[props.messageID] + if (parts) { + for (const part of parts) { + delete draft.part_text_accum_delta[part.id] + } + } delete draft.part[props.messageID] }), ) @@ -219,6 +225,11 @@ export function applyDirectoryEvent(input: { case "message.part.updated": { const part = (event.properties as { part: Part }).part if (SKIP_PARTS.has(part.type)) break + input.setStore( + produce((draft) => { + delete draft.part_text_accum_delta[part.id] + }), + ) const parts = input.store.part[part.messageID] if (!parts) { input.setStore("part", part.messageID, [part]) @@ -240,6 +251,11 @@ export function applyDirectoryEvent(input: { } case "message.part.removed": { const props = event.properties as { messageID: string; partID: string } + input.setStore( + produce((draft) => { + delete draft.part_text_accum_delta[props.partID] + }), + ) const parts = input.store.part[props.messageID] if (!parts) break const result = Binary.search(parts, props.partID, (p) => p.id) @@ -263,6 +279,7 @@ export function applyDirectoryEvent(input: { if (!parts) break const result = Binary.search(parts, props.partID, (p) => p.id) if (!result.found) break + input.setStore("part_text_accum_delta", props.partID, (existing) => (existing ?? "") + props.delta) input.setStore( "part", props.messageID, diff --git a/packages/app/src/context/global-sync/session-cache.test.ts b/packages/app/src/context/global-sync/session-cache.test.ts index 472ac219e..4b2be505e 100644 --- a/packages/app/src/context/global-sync/session-cache.test.ts +++ b/packages/app/src/context/global-sync/session-cache.test.ts @@ -39,6 +39,7 @@ describe("app session cache", () => { part: Record permission: Record question: Record + part_text_accum_delta: Record } = { session_status: { ses_1: { type: "busy" } as SessionStatus }, session_diff: { ses_1: [] }, @@ -47,12 +48,14 @@ describe("app session cache", () => { part: { msg_1: [part("prt_1", "ses_1", "msg_1")] }, permission: { ses_1: [] as PermissionRequest[] }, question: { ses_1: [] as QuestionRequest[] }, + part_text_accum_delta: { prt_1: "streamed text" }, } dropSessionCaches(store, ["ses_1"]) expect(store.message.ses_1).toBeUndefined() expect(store.part.msg_1).toBeUndefined() + expect(store.part_text_accum_delta.prt_1).toBeUndefined() expect(store.todo.ses_1).toBeUndefined() expect(store.session_diff.ses_1).toBeUndefined() expect(store.session_status.ses_1).toBeUndefined() @@ -70,6 +73,7 @@ describe("app session cache", () => { part: Record permission: Record question: Record + part_text_accum_delta: Record } = { session_status: {}, session_diff: {}, @@ -78,6 +82,7 @@ describe("app session cache", () => { part: { [m.id]: [part("prt_1", "ses_1", m.id)] }, permission: {}, question: {}, + part_text_accum_delta: {}, } dropSessionCaches(store, ["ses_1"]) diff --git a/packages/app/src/context/global-sync/session-cache.ts b/packages/app/src/context/global-sync/session-cache.ts index 6f4d81062..05cdc8464 100644 --- a/packages/app/src/context/global-sync/session-cache.ts +++ b/packages/app/src/context/global-sync/session-cache.ts @@ -18,6 +18,7 @@ type SessionCache = { part: Record permission: Record question: Record + part_text_accum_delta: Record } export function dropSessionCaches(store: SessionCache, sessionIDs: Iterable) { @@ -27,6 +28,9 @@ export function dropSessionCaches(store: SessionCache, sessionIDs: Iterable stale.has(part?.sessionID ?? ""))) continue + for (const part of parts) { + delete store.part_text_accum_delta[part.id] + } delete store.part[key] } diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index e3ec83c5e..6bf42a073 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -72,6 +72,9 @@ export type State = { part: { [messageID: string]: Part[] } + part_text_accum_delta: { + [partID: string]: string + } } export type VcsCache = { diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 137f68975..7a7d5b15f 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -1461,7 +1461,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { const streaming = createMemo( () => props.message.role === "assistant" && typeof (props.message as AssistantMessage).time.completed !== "number", ) - const text = () => (part().text ?? "").trim() + const text = () => (data.store.part_text_accum_delta?.[part().id] ?? part().text ?? "").trim() const isLastTextPart = createMemo(() => { const last = (data.store.part?.[props.message.id] ?? []) .filter((item): item is TextPart => item?.type === "text" && !!item.text?.trim()) @@ -1521,11 +1521,12 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { } PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) { + const data = useData() const part = () => props.part as ReasoningPart const streaming = createMemo( () => props.message.role === "assistant" && typeof (props.message as AssistantMessage).time.completed !== "number", ) - const text = () => part().text.trim() + const text = () => (data.store.part_text_accum_delta?.[part().id] ?? part().text).trim() return ( diff --git a/packages/ui/src/context/data.tsx b/packages/ui/src/context/data.tsx index 632bed0cf..3d015257f 100644 --- a/packages/ui/src/context/data.tsx +++ b/packages/ui/src/context/data.tsx @@ -24,6 +24,9 @@ type Data = { part: { [messageID: string]: Part[] } + part_text_accum_delta?: { + [partID: string]: string + } } export type NavigateToSessionFn = (sessionID: string) => void From 7e997cfba418f481e08b06214907ebf938c5f6dd Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Mon, 11 May 2026 12:57:26 +0530 Subject: [PATCH 011/378] refactor(scout): resolve configured reference mentions (#26701) --- packages/opencode/src/agent/agent.ts | 71 -------- packages/opencode/src/session/prompt.ts | 170 +++++++++++++++++- .../src/v2/session-message-updater.ts | 1 + packages/opencode/src/v2/session-message.ts | 1 + packages/opencode/src/v2/session-prompt.ts | 13 ++ packages/opencode/test/agent/agent.test.ts | 52 +----- packages/opencode/test/session/prompt.test.ts | 97 ++++++++++ .../test/session/snapshot-tool-race.test.ts | 1 + packages/sdk/js/src/v2/gen/types.gen.ts | 14 ++ 9 files changed, 304 insertions(+), 116 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 5917240cd..777f6e6d1 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -26,7 +26,6 @@ import * as Option from "effect/Option" import * as OtelTracer from "@effect/opentelemetry/Tracer" import { zod } from "@opencode-ai/core/effect-zod" import { withStatics, type DeepMutable } from "@opencode-ai/core/schema" -import { Reference } from "@/reference/reference" export const Info = Schema.Struct({ name: Schema.String, @@ -301,76 +300,6 @@ export const layer = Layer.effect( item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {})) } - function referencePrompt(reference: Reference.Resolved) { - if (reference.kind === "local") { - return [ - `You are configured reference @${reference.name}, a read-only research agent for external reference material.`, - `Local directory: ${reference.path}`, - `Inspect this directory as the primary reference source. Prefer repo_overview with path ${JSON.stringify(reference.path)} before broader searches. Do not edit files.`, - `Return exact absolute file paths for findings whenever possible.`, - ].join("\n\n") - } - - if (reference.kind === "invalid") { - return [ - `You are configured reference @${reference.name}, but this reference is not usable yet.`, - `Configured repository: ${reference.repository}`, - `Problem: ${reference.message}`, - `Explain this configuration problem if invoked. Do not edit files or attempt fallback clones.`, - ].join("\n\n") - } - - return [ - `You are configured reference @${reference.name}, a read-only research agent for external reference material.`, - `Repository: ${reference.repository}`, - ...(reference.branch ? [`Branch/ref: ${reference.branch}`] : []), - `Cached directory: ${reference.path}`, - `OpenCode materializes this configured repository before use. Do not call repo_clone for this reference.`, - `Inspect the cached directory as the primary reference source. Prefer repo_overview with path ${JSON.stringify(reference.path)} before broader searches, then use Glob, Grep, and Read inside that directory. Do not edit files.`, - `Return exact absolute file paths for findings whenever possible.`, - ].join("\n\n") - } - - function referenceDescription(reference: Reference.Resolved) { - if (reference.kind === "local") return `Scout reference for local directory ${reference.path}` - if (reference.kind === "git") return `Scout reference for repository ${reference.repository}` - return `Invalid Scout reference for repository ${reference.repository}` - } - - if (Flag.OPENCODE_EXPERIMENTAL_SCOUT) { - const resolvedReferences = Reference.resolveAll({ - references: cfg.reference ?? {}, - directory: ctx.directory, - worktree: ctx.worktree, - }) - for (const resolved of resolvedReferences) { - if (agents[resolved.name]) continue - const localPath = resolved.kind === "invalid" ? undefined : resolved.path - agents[resolved.name] = { - name: resolved.name, - description: referenceDescription(resolved), - permission: Permission.merge( - agents.scout.permission, - Permission.fromConfig({ - repo_clone: "deny", - ...(localPath - ? { - external_directory: { - [localPath]: "allow", - [path.join(localPath, "*")]: "allow", - }, - } - : {}), - }), - ), - prompt: referencePrompt(resolved), - options: { reference: cfg.reference?.[resolved.name], resolved }, - mode: "subagent", - native: false, - } - } - } - // Ensure Truncate.GLOB is allowed unless explicitly configured for (const name in agents) { const agent = agents[name] diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 7f4f60855..3b919e2f0 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -56,7 +56,8 @@ import { EffectBridge } from "@/effect/bridge" import { SyncEvent } from "@/sync" import { SessionEvent } from "@/v2/session-event" import { Modelv2 } from "@/v2/model" -import { AgentAttachment, FileAttachment, Source } from "@/v2/session-prompt" +import { AgentAttachment, FileAttachment, ReferenceAttachment, Source } from "@/v2/session-prompt" +import { Reference } from "@/reference/reference" import * as DateTime from "effect/DateTime" import { eq } from "@/storage/db" import * as Database from "@/storage/db" @@ -81,6 +82,45 @@ const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested struc const log = Log.create({ service: "session.prompt" }) const elog = EffectLogger.create({ service: "session.prompt" }) +type ReferencePromptMetadata = { + name: string + kind: "local" | "git" | "invalid" + path?: string + repository?: string + branch?: string + target?: string + targetPath?: string + problem?: string + source: { value: string; start: number; end: number } +} + +function stringField(record: Record, key: string) { + return typeof record[key] === "string" ? record[key] : undefined +} + +function referencePromptMetadata(input: unknown): ReferencePromptMetadata | undefined { + if (!input || typeof input !== "object" || Array.isArray(input)) return + const record = input as Record + const name = stringField(record, "name") + const kind = stringField(record, "kind") + if (!name || (kind !== "local" && kind !== "git" && kind !== "invalid")) return + if (!record.source || typeof record.source !== "object" || Array.isArray(record.source)) return + const source = record.source as Record + const value = stringField(source, "value") + if (!value || typeof source.start !== "number" || typeof source.end !== "number") return + return { + name, + kind, + path: stringField(record, "path"), + repository: stringField(record, "repository"), + branch: stringField(record, "branch"), + target: stringField(record, "target"), + targetPath: stringField(record, "targetPath"), + problem: stringField(record, "problem"), + source: { value, start: source.start, end: source.end }, + } +} + export interface Interface { readonly cancel: (sessionID: SessionID) => Effect.Effect readonly prompt: (input: PromptInput) => Effect.Effect @@ -119,6 +159,7 @@ export const layer = Layer.effect( const summary = yield* SessionSummary.Service const sys = yield* SystemPrompt.Service const llm = yield* LLM.Service + const references = yield* Reference.Service const sync = yield* SyncEvent.Service const runner = Effect.fn("SessionPrompt.runner")(function* () { return yield* EffectBridge.make() @@ -141,12 +182,116 @@ export const layer = Layer.effect( const parts: Types.DeepMutable = [{ type: "text", text: template }] const files = ConfigMarkdown.files(template) const seen = new Set() + const mentionSource = (match: RegExpMatchArray) => { + const start = match.index ?? 0 + return { value: match[0], start, end: start + match[0].length } + } + const referenceTextPart = (input: { + reference: Reference.Resolved + source: ReturnType + target?: string + targetPath?: string + problem?: string + }): MessageV2.TextPartInput => { + const metadata: ReferencePromptMetadata = { + name: input.reference.name, + kind: input.reference.kind, + ...(input.reference.kind === "invalid" + ? { repository: input.reference.repository } + : { path: input.reference.path }), + ...(input.reference.kind === "git" + ? { repository: input.reference.repository, branch: input.reference.branch } + : {}), + ...(input.target === undefined ? {} : { target: input.target }), + ...(input.targetPath ? { targetPath: input.targetPath } : {}), + problem: input.problem ?? (input.reference.kind === "invalid" ? input.reference.message : undefined), + source: input.source, + } + const label = metadata.target === undefined ? `@${metadata.name}` : `@${metadata.name}/${metadata.target}` + return { + type: "text", + synthetic: true, + text: [ + `Referenced configured reference ${label}.`, + ...(metadata.kind === "local" ? ["Kind: local directory"] : []), + ...(metadata.kind === "git" ? ["Kind: git repository"] : []), + ...(metadata.repository ? [`Repository: ${metadata.repository}`] : []), + ...(metadata.branch ? [`Branch/ref: ${metadata.branch}`] : []), + ...(metadata.path ? [`Reference root: ${metadata.path}`] : []), + ...(metadata.targetPath ? [`Resolved path: ${metadata.targetPath}`] : []), + ...(metadata.problem + ? [`Problem: ${metadata.problem}`] + : [ + "For targeted context, inspect the reference path directly with Read, Glob, and Grep. For broader research, call the task tool with subagent scout and include this reference path.", + ]), + ].join("\n"), + metadata: { reference: metadata }, + } + } yield* Effect.forEach( files, Effect.fnUntraced(function* (match) { const name = match[1] + if (!name) return if (seen.has(name)) return seen.add(name) + + const slash = name.indexOf("/") + const alias = slash === -1 ? name : name.slice(0, slash) + const reference = yield* references.get(alias) + if (reference) { + const source = mentionSource(match) + if (reference.kind === "invalid") { + parts.push( + referenceTextPart({ reference, source, target: slash === -1 ? undefined : name.slice(slash + 1) }), + ) + return + } + + yield* references.ensure(reference.path) + if (slash === -1) { + parts.push(referenceTextPart({ reference, source })) + return + } + + const target = name.slice(slash + 1) + const targetPath = path.resolve(reference.path, target) + if (!AppFileSystem.contains(reference.path, targetPath)) { + parts.push( + referenceTextPart({ + reference, + source, + target, + targetPath, + problem: `Path escapes configured reference @${alias}: ${target}`, + }), + ) + return + } + + const info = yield* fsys.stat(targetPath).pipe(Effect.option) + if (Option.isNone(info)) { + parts.push( + referenceTextPart({ + reference, + source, + target, + targetPath, + problem: `Path does not exist inside configured reference @${alias}: ${target}`, + }), + ) + return + } + + parts.push({ + type: "file", + url: pathToFileURL(targetPath).href, + filename: name, + mime: info.value.type === "Directory" ? "application/x-directory" : "text/plain", + }) + return + } + const filepath = name.startsWith("~/") ? path.join(os.homedir(), name.slice(2)) : path.resolve(ctx.worktree, name) @@ -1326,6 +1471,26 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (part.type === "text") { if (part.synthetic) result.synthetic.push(part.text) else result.text.push(part.text) + const reference = referencePromptMetadata(part.metadata?.reference) + if (reference) { + result.references.push( + new ReferenceAttachment({ + name: reference.name, + kind: reference.kind, + uri: reference.path ? pathToFileURL(reference.path).href : undefined, + repository: reference.repository, + branch: reference.branch, + target: reference.target, + targetUri: reference.targetPath ? pathToFileURL(reference.targetPath).href : undefined, + problem: reference.problem, + source: new Source({ + start: reference.source.start, + end: reference.source.end, + text: reference.source.value, + }), + }), + ) + } } if (part.type === "file") { result.files.push( @@ -1363,6 +1528,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the text: [] as string[], files: [] as FileAttachment[], agents: [] as AgentAttachment[], + references: [] as ReferenceAttachment[], synthetic: [] as string[], }, ) @@ -1375,6 +1541,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the text: nextPrompt.text.join("\n"), files: nextPrompt.files, agents: nextPrompt.agents, + references: nextPrompt.references, }, }) } @@ -1817,6 +1984,7 @@ export const defaultLayer = Layer.suspend(() => Agent.defaultLayer, SystemPrompt.defaultLayer, LLM.defaultLayer, + Reference.defaultLayer, Bus.layer, CrossSpawnSpawner.defaultLayer, SyncEvent.defaultLayer, diff --git a/packages/opencode/src/v2/session-message-updater.ts b/packages/opencode/src/v2/session-message-updater.ts index 80ecb1011..bbdf59c55 100644 --- a/packages/opencode/src/v2/session-message-updater.ts +++ b/packages/opencode/src/v2/session-message-updater.ts @@ -123,6 +123,7 @@ export function update(adapter: Adapter, event: SessionEvent.Eve text: event.data.prompt.text, files: event.data.prompt.files, agents: event.data.prompt.agents, + references: event.data.prompt.references, time: { created: event.data.timestamp }, }), ) diff --git a/packages/opencode/src/v2/session-message.ts b/packages/opencode/src/v2/session-message.ts index 024e28c45..62fc75fc8 100644 --- a/packages/opencode/src/v2/session-message.ts +++ b/packages/opencode/src/v2/session-message.ts @@ -34,6 +34,7 @@ export class User extends Schema.Class("Session.Message.User")({ text: Prompt.fields.text, files: Prompt.fields.files, agents: Prompt.fields.agents, + references: Prompt.fields.references, type: Schema.Literal("user"), time: Schema.Struct({ created: V2Schema.DateTimeUtcFromMillis, diff --git a/packages/opencode/src/v2/session-prompt.ts b/packages/opencode/src/v2/session-prompt.ts index 86d8e52eb..14167fc28 100644 --- a/packages/opencode/src/v2/session-prompt.ts +++ b/packages/opencode/src/v2/session-prompt.ts @@ -29,8 +29,21 @@ export class AgentAttachment extends Schema.Class("Prompt.Agent source: Source.pipe(Schema.optional), }) {} +export class ReferenceAttachment extends Schema.Class("Prompt.ReferenceAttachment")({ + name: Schema.String, + kind: Schema.Literals(["local", "git", "invalid"]), + uri: Schema.String.pipe(Schema.optional), + repository: Schema.String.pipe(Schema.optional), + branch: Schema.String.pipe(Schema.optional), + target: Schema.String.pipe(Schema.optional), + targetUri: Schema.String.pipe(Schema.optional), + problem: Schema.String.pipe(Schema.optional), + source: Source.pipe(Schema.optional), +}) {} + export class Prompt extends Schema.Class("Prompt")({ text: Schema.String, files: Schema.Array(FileAttachment).pipe(Schema.optional), agents: Schema.Array(AgentAttachment).pipe(Schema.optional), + references: Schema.Array(ReferenceAttachment).pipe(Schema.optional), }) {} diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index cb6f60503..df68fdfdc 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -141,16 +141,12 @@ test("scout agent allows repo cloning and repo cache reads", async () => { }) }) -test("reference config creates scout-backed subagents", async () => { +test("reference config does not create subagents", async () => { await withExperimentalScout(true, async () => { await using tmp = await tmpdir({ config: { reference: { effect: "github.com/effect/effect-smol", - effectDev: { - repository: "https://github.com/effect/effect-smol", - branch: "dev", - }, effectFull: { repository: "Effect-TS/effect", branch: "main", @@ -165,45 +161,13 @@ test("reference config creates scout-backed subagents", async () => { await WithInstance.provide({ directory: tmp.path, fn: async () => { - const effect = await load(tmp.path, (svc) => svc.get("effect")) - const effectDev = await load(tmp.path, (svc) => svc.get("effectDev")) - const effectFull = await load(tmp.path, (svc) => svc.get("effectFull")) - const local = await load(tmp.path, (svc) => svc.get("localdocs")) - const localFull = await load(tmp.path, (svc) => svc.get("localdocsFull")) - - expect(effect).toBeDefined() - expect(effect?.mode).toBe("subagent") - expect(effect?.prompt).toContain("Repository: github.com/effect/effect-smol") - expect(effect?.prompt).toContain( - `Cached directory: ${path.join(Global.Path.repos, "github.com", "effect", "effect-smol")}`, - ) - expect(effect?.prompt).toContain("Do not call repo_clone") - expect(evalPerm(effect, "repo_clone")).toBe("deny") - - expect(effectDev).toBeDefined() - expect(effectDev?.prompt).toContain("Problem: Reference conflicts with @effect") - expect(effectDev?.prompt).not.toContain("Cached directory:") - - expect(effectFull).toBeDefined() - expect(effectFull?.mode).toBe("subagent") - expect(effectFull?.prompt).toContain("Repository: Effect-TS/effect") - expect(effectFull?.prompt).toContain("Branch/ref: main") - expect(evalPerm(effectFull, "repo_clone")).toBe("deny") - - expect(local).toBeDefined() - expect(local?.mode).toBe("subagent") - expect(local?.prompt).toContain(`Local directory: ${path.resolve(tmp.path, "../docs")}`) - expect( - Permission.evaluate( - "external_directory", - path.join(path.resolve(tmp.path, "../docs"), "README.md"), - local!.permission, - ).action, - ).toBe("allow") - - expect(localFull).toBeDefined() - expect(localFull?.mode).toBe("subagent") - expect(localFull?.prompt).toContain(`Local directory: ${path.resolve(tmp.path, "../local-docs")}`) + const agents = await load(tmp.path, (svc) => svc.list()) + const names = agents.map((agent) => agent.name) + expect(names).toContain("scout") + expect(names).not.toContain("effect") + expect(names).not.toContain("effectFull") + expect(names).not.toContain("localdocs") + expect(names).not.toContain("localdocsFull") }, }) }) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 42c9a81cd..cb771aee3 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -2,6 +2,7 @@ import { NodeFileSystem } from "@effect/platform-node" import { FetchHttpClient } from "effect/unstable/http" import { expect } from "bun:test" import { Cause, Effect, Exit, Fiber, Layer } from "effect" +import fs from "fs/promises" import path from "path" import { fileURLToPath } from "url" import { NamedError } from "@opencode-ai/core/util/error" @@ -203,6 +204,7 @@ function makeHttp() { SessionPrompt.layer.pipe( Layer.provide(SessionRevert.defaultLayer), Layer.provide(Image.defaultLayer), + Layer.provide(Reference.defaultLayer), Layer.provide(summary), Layer.provideMerge(run), Layer.provideMerge(compact), @@ -1791,6 +1793,101 @@ it.live("keeps stored part order stable when file resolution is async", () => ), ) +it.live("resolves configured reference mentions before workspace paths and agents", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const docs = path.join(dir, "external-docs") + yield* Effect.promise(() => fs.mkdir(path.join(docs, "guide"), { recursive: true })) + yield* Effect.promise(() => fs.mkdir(path.join(dir, "docs"), { recursive: true })) + yield* Effect.promise(() => Bun.write(path.join(docs, "README.md"), "reference readme")) + yield* Effect.promise(() => Bun.write(path.join(docs, "guide", "intro.md"), "reference intro")) + yield* Effect.promise(() => Bun.write(path.join(dir, "docs", "README.md"), "workspace readme")) + + const prompt = yield* SessionPrompt.Service + const parts = yield* prompt.resolvePromptParts( + "Use @docs and @docs/README.md and @docs/guide and @docs/missing.md and @docs/README.md and @build", + ) + const references = parts.filter( + (part): part is MessageV2.TextPartInput => + part.type === "text" && part.synthetic === true && part.text.startsWith("Referenced configured reference "), + ) + const files = parts.filter((part): part is MessageV2.FilePartInput => part.type === "file") + const agents = parts.filter((part): part is MessageV2.AgentPartInput => part.type === "agent") + const bare = references.find((part) => part.text.includes("@docs.")) + const missing = references.find((part) => part.text.includes("@docs/missing.md")) + const guide = files.find((part) => part.filename === "docs/guide") + + expect(references.length).toBe(2) + expect(bare?.metadata?.reference).toMatchObject({ + name: "docs", + kind: "local", + path: docs, + }) + expect(missing?.text).toContain("Path does not exist inside configured reference @docs") + expect(missing?.metadata?.reference).toMatchObject({ + target: "missing.md", + targetPath: path.join(docs, "missing.md"), + }) + + expect(files.length).toBe(2) + expect(files.map((file) => fileURLToPath(file.url)).sort()).toEqual( + [path.join(docs, "README.md"), path.join(docs, "guide")].sort(), + ) + expect(guide?.mime).toBe("application/x-directory") + expect(agents.map((agent) => agent.name)).toEqual(["build"]) + }), + { + git: true, + config: { + ...cfg, + reference: { + docs: "./external-docs", + }, + }, + }, + ), +) + +it.live("injects metadata for bare configured reference mentions", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const docs = path.join(dir, "external-docs") + yield* Effect.promise(() => fs.mkdir(docs, { recursive: true })) + + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + const message = yield* prompt.prompt({ + sessionID: session.id, + noReply: true, + parts: yield* prompt.resolvePromptParts("Use @docs for context"), + }) + + const stored = MessageV2.get({ sessionID: session.id, messageID: message.info.id }) + const synthetic = stored.parts + .filter((part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true) + const reference = synthetic.find((part) => part.text.startsWith("Referenced configured reference @docs.")) + + expect(reference?.metadata?.reference).toMatchObject({ name: "docs", kind: "local", path: docs }) + expect(synthetic.some((part) => part.text.includes(`Reference root: ${docs}`))).toBe(true) + expect(synthetic.some((part) => part.text.includes("subagent scout"))).toBe(true) + + yield* sessions.remove(session.id) + }), + { + git: true, + config: { + ...cfg, + reference: { + docs: "./external-docs", + }, + }, + }, + ), +) + // Special characters in filenames it.live("handles filenames with # character", () => diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 5c47df4c0..8640612e9 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -154,6 +154,7 @@ function makeHttp() { SessionPrompt.layer.pipe( Layer.provide(SessionRevert.defaultLayer), Layer.provide(Image.defaultLayer), + Layer.provide(Reference.defaultLayer), Layer.provide(SessionSummary.defaultLayer), Layer.provideMerge(run), Layer.provideMerge(compact), diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 5bba8efc0..da80645ad 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -771,6 +771,7 @@ export type Prompt = { text: string files?: Array agents?: Array + references?: Array } export type GlobalEvent = { @@ -2718,6 +2719,18 @@ export type PromptAgentAttachment = { source?: PromptSource } +export type PromptReferenceAttachment = { + name: string + kind: "local" | "git" | "invalid" + uri?: string + repository?: string + branch?: string + target?: string + targetUri?: string + problem?: string + source?: PromptSource +} + export type EventSessionNextPrompted = { id: string type: "session.next.prompted" @@ -3121,6 +3134,7 @@ export type SessionMessageUser = { text: string files?: Array agents?: Array + references?: Array type: "user" } From 3bd98ea05500c8b93b0270b9131cb53d94a635f5 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 07:28:32 +0000 Subject: [PATCH 012/378] chore: generate --- packages/opencode/test/session/prompt.test.ts | 5 +- packages/sdk/openapi.json | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index cb771aee3..f5c167465 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1866,8 +1866,9 @@ it.live("injects metadata for bare configured reference mentions", () => }) const stored = MessageV2.get({ sessionID: session.id, messageID: message.info.id }) - const synthetic = stored.parts - .filter((part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true) + const synthetic = stored.parts.filter( + (part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true, + ) const reference = synthetic.find((part) => part.text.startsWith("Referenced configured reference @docs.")) expect(reference?.metadata?.reference).toMatchObject({ name: "docs", kind: "local", path: docs }) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index fb29d68ed..df0427f45 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11120,6 +11120,12 @@ "items": { "$ref": "#/components/schemas/PromptAgentAttachment" } + }, + "references": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PromptReferenceAttachment" + } } }, "required": ["text"], @@ -17039,6 +17045,41 @@ "required": ["name"], "additionalProperties": false }, + "PromptReferenceAttachment": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": ["local", "git", "invalid"] + }, + "uri": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "target": { + "type": "string" + }, + "targetUri": { + "type": "string" + }, + "problem": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/PromptSource" + } + }, + "required": ["name", "kind"], + "additionalProperties": false + }, "EventSessionNextPrompted": { "type": "object", "properties": { @@ -18231,6 +18272,12 @@ "$ref": "#/components/schemas/PromptAgentAttachment" } }, + "references": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PromptReferenceAttachment" + } + }, "type": { "type": "string", "enum": ["user"] From 2d0d3d596ec06e0d83216ece48001f190cc4581b Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Mon, 11 May 2026 13:40:36 +0530 Subject: [PATCH 013/378] feat(compaction): serialize compaction tail (#26830) --- packages/opencode/src/config/config.ts | 4 +- packages/opencode/src/session/compaction.ts | 60 +++++++++++-------- packages/opencode/src/session/message-v2.ts | 42 +------------ .../opencode/test/session/compaction.test.ts | 50 +++++++++------- .../test/session/messages-pagination.test.ts | 16 ++--- 5 files changed, 77 insertions(+), 95 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 41ccac749..114a38803 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -273,10 +273,10 @@ export const Info = Schema.Struct({ }), tail_turns: Schema.optional(NonNegativeInt).annotate({ description: - "Number of recent user turns, including their following assistant/tool responses, to keep verbatim during compaction (default: 2)", + "Number of recent user turns, including their following assistant/tool responses, to serialize into the compaction summary (default: 2)", }), preserve_recent_tokens: Schema.optional(NonNegativeInt).annotate({ - description: "Maximum number of tokens from recent turns to preserve verbatim after compaction", + description: "Maximum number of tokens from recent turns to serialize into the compaction summary", }), reserved: Schema.optional(NonNegativeInt).annotate({ description: "Token buffer for compaction. Leaves enough window to avoid overflow during compaction.", diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 4eafbdf74..3ca4f074f 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -79,12 +79,10 @@ Rules: type Turn = { start: number end: number - id: MessageID } type Tail = { start: number - id: MessageID } type CompletedCompaction = { @@ -121,19 +119,41 @@ function completedCompactions(messages: MessageV2.WithParts[]) { }) } -function buildPrompt(input: { previousSummary?: string; context: string[] }) { +function buildPrompt(input: { previousSummary?: string; context: string[]; tail?: string }) { + const source = input.tail + ? "the conversation history above and the serialized recent conversation tail below" + : "the conversation history above" const anchor = input.previousSummary ? [ - "Update the anchored summary below using the conversation history above.", + `Update the anchored summary below using ${source}.`, "Preserve still-true details, remove stale details, and merge in the new facts.", "", input.previousSummary, "", ].join("\n") - : "Create a new anchored summary from the conversation history above." - return [anchor, SUMMARY_TEMPLATE, ...input.context].join("\n\n") + : `Create a new anchored summary from ${source}.` + const tail = input.tail + ? [ + "Fold this serialized recent conversation tail into the summary; it is not provider message history.", + "", + input.tail, + "", + ].join("\n") + : undefined + return [anchor, ...(tail ? [tail] : []), SUMMARY_TEMPLATE, ...input.context].join("\n\n") } +const serialize = Effect.fn("SessionCompaction.serialize")(function* (input: { + messages: MessageV2.WithParts[] + model: Provider.Model +}) { + const messages = yield* MessageV2.toModelMessagesEffect(input.messages, input.model, { + stripMedia: true, + toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS, + }) + return messages.length ? JSON.stringify(messages, null, 2) : undefined +}) + function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model }) { return ( input.cfg.compaction?.preserve_recent_tokens ?? @@ -150,7 +170,6 @@ function turns(messages: MessageV2.WithParts[]) { result.push({ start: i, end: messages.length, - id: msg.info.id, }) } for (let i = 0; i < result.length - 1; i++) { @@ -177,7 +196,6 @@ function splitTurn(input: { if (size > input.budget) continue return { start, - id: input.messages[start]!.info.id, } satisfies Tail } return undefined @@ -244,8 +262,7 @@ export const layer: Layer.Layer< messages: MessageV2.WithParts[] model: Provider.Model }) { - const msgs = yield* MessageV2.toModelMessagesEffect(input.messages, input.model) - return Token.estimate(JSON.stringify(msgs)) + return Token.estimate((yield* serialize(input)) ?? "") }) const select = Effect.fn("SessionCompaction.select")(function* (input: { @@ -254,10 +271,10 @@ export const layer: Layer.Layer< model: Provider.Model }) { const limit = input.cfg.compaction?.tail_turns ?? DEFAULT_TAIL_TURNS - if (limit <= 0) return { head: input.messages, tail_start_id: undefined } + if (limit <= 0) return { head: input.messages, tail: [] } const budget = preserveRecentBudget({ cfg: input.cfg, model: input.model }) const all = turns(input.messages) - if (!all.length) return { head: input.messages, tail_start_id: undefined } + if (!all.length) return { head: input.messages, tail: [] } const recent = all.slice(-limit) const sizes = yield* Effect.forEach( recent, @@ -276,7 +293,7 @@ export const layer: Layer.Layer< const size = sizes[i] if (total + size <= budget) { total += size - keep = { start: turn.start, id: turn.id } + keep = { start: turn.start } continue } const remaining = budget - total @@ -292,10 +309,10 @@ export const layer: Layer.Layer< break } - if (!keep || keep.start === 0) return { head: input.messages, tail_start_id: undefined } + if (!keep) return { head: input.messages, tail: [] } return { head: input.messages.slice(0, keep.start), - tail_start_id: keep.id, + tail: input.messages.slice(keep.start), } }) @@ -406,7 +423,10 @@ export const layer: Layer.Layer< { sessionID: input.sessionID }, { context: [], prompt: undefined }, ) - const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context }) + const tailMessages = structuredClone(selected.tail) + yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: tailMessages }) + const tail = yield* serialize({ messages: tailMessages, model }) + const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context, tail }) const msgs = structuredClone(selected.head) yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, { @@ -473,13 +493,6 @@ export const layer: Layer.Layer< return "stop" } - if (compactionPart && selected.tail_start_id && compactionPart.tail_start_id !== selected.tail_start_id) { - yield* session.updatePart({ - ...compactionPart, - tail_start_id: selected.tail_start_id, - }) - } - if (result === "continue" && input.auto) { if (replay) { const original = replay.info @@ -575,7 +588,6 @@ export const layer: Layer.Layer< sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), text: summary ?? "", - include: selected.tail_start_id, }) } yield* bus.publish(Event.Compacted, { sessionID: input.sessionID }) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index a8c8dabc8..e3539021b 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -840,12 +840,13 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* ( return part.metadata?.anthropic?.signature != null }) for (const part of msg.parts) { + if (msg.info.summary && part.type !== "text") continue if (part.type === "text") { const text = part.text === "" && hasSignedReasoning ? " " : part.text assistantMessage.parts.push({ type: "text", text, - ...(differentModel ? {} : { providerMetadata: part.metadata }), + ...(differentModel || msg.info.summary ? {} : { providerMetadata: part.metadata }), }) } if (part.type === "step-start") @@ -1071,53 +1072,16 @@ export function get(input: { sessionID: SessionID; messageID: MessageID }): With export function filterCompacted(msgs: Iterable) { const result = [] as WithParts[] const completed = new Set() - let retain: MessageID | undefined for (const msg of msgs) { result.push(msg) - if (retain) { - if (msg.info.id === retain) break - continue - } if (msg.info.role === "user" && completed.has(msg.info.id)) { - const part = msg.parts.find((item): item is CompactionPart => item.type === "compaction") - if (!part) continue - if (!part.tail_start_id) break - retain = part.tail_start_id - if (msg.info.id === retain) break + if (msg.parts.some((item): item is CompactionPart => item.type === "compaction")) break continue } - if (msg.info.role === "user" && completed.has(msg.info.id) && msg.parts.some((part) => part.type === "compaction")) - break if (msg.info.role === "assistant" && msg.info.summary && msg.info.finish && !msg.info.error) completed.add(msg.info.parentID) } result.reverse() - const compactionIndex = result.findLastIndex( - (msg) => - msg.info.role === "user" && - msg.parts.some((item): item is CompactionPart => item.type === "compaction" && item.tail_start_id !== undefined), - ) - const compaction = result[compactionIndex] - const part = compaction?.parts.find( - (item): item is CompactionPart => item.type === "compaction" && item.tail_start_id !== undefined, - ) - const summaryIndex = compaction - ? result.findIndex( - (msg, index) => - index > compactionIndex && - msg.info.role === "assistant" && - msg.info.summary && - msg.info.parentID === compaction.info.id, - ) - : -1 - const tailIndex = part?.tail_start_id ? result.findIndex((msg) => msg.info.id === part.tail_start_id) : -1 - if (tailIndex >= 0 && tailIndex < compactionIndex && summaryIndex > compactionIndex) { - return [ - ...result.slice(compactionIndex, summaryIndex + 1), - ...result.slice(tailIndex, compactionIndex), - ...result.slice(summaryIndex + 1), - ] - } return result } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 8f987b4d1..13400d79c 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -926,12 +926,12 @@ describe("session.compaction.process", () => { ) itCompaction.instance( - "persists tail_start_id for retained recent turns", + "does not persist tail_start_id for serialized recent turns", Effect.gen(function* () { const ssn = yield* SessionNs.Service const session = yield* ssn.create({}) yield* createUserMessage(session.id, "first") - const keep = yield* createUserMessage(session.id, "second") + yield* createUserMessage(session.id, "second") yield* createUserMessage(session.id, "third") yield* createSummaryCompaction(session.id) @@ -947,18 +947,18 @@ describe("session.compaction.process", () => { const part = yield* readCompactionPart(session.id) expect(part?.type).toBe("compaction") - expect(part?.tail_start_id).toBe(keep.id) + expect(part?.tail_start_id).toBeUndefined() }).pipe(withCompaction({ config: cfg({ tail_turns: 2, preserve_recent_tokens: 10_000 }) })), ) itCompaction.instance( - "shrinks retained tail to fit preserve token budget", + "does not persist tail_start_id when shrinking serialized tail", Effect.gen(function* () { const ssn = yield* SessionNs.Service const session = yield* ssn.create({}) yield* createUserMessage(session.id, "first") yield* createUserMessage(session.id, "x".repeat(2_000)) - const keep = yield* createUserMessage(session.id, "tiny") + yield* createUserMessage(session.id, "tiny") yield* createSummaryCompaction(session.id) const msgs = yield* ssn.messages({ sessionID: session.id }) @@ -973,7 +973,7 @@ describe("session.compaction.process", () => { const part = yield* readCompactionPart(session.id) expect(part?.type).toBe("compaction") - expect(part?.tail_start_id).toBe(keep.id) + expect(part?.tail_start_id).toBeUndefined() }).pipe(withCompaction({ config: cfg({ tail_turns: 2, preserve_recent_tokens: 100 }) })), ) @@ -1005,7 +1005,7 @@ describe("session.compaction.process", () => { ) itCompaction.instance( - "falls back to full summary when retained tail media exceeds preserve token budget", + "serializes retained tail media as text in the summary input", () => { const stub = llm() let captured = "" @@ -1078,15 +1078,16 @@ describe("session.compaction.process", () => { const part = yield* readCompactionPart(session.id) expect(part?.type).toBe("compaction") - expect(part?.tail_start_id).toBe(keep.id) + expect(part?.tail_start_id).toBeUndefined() expect(captured).toContain("zzzz") - expect(captured).not.toContain("keep tail") + expect(captured).toContain("keep tail") const filtered = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(filtered.map((msg) => msg.info.id).slice(0, 3)).toEqual([parent!, expect.any(String), keep.id]) + expect(filtered.map((msg) => msg.info.id)).toEqual([parent!, expect.any(String)]) expect(filtered[1]?.info.role).toBe("assistant") expect(filtered[1]?.info.role === "assistant" ? filtered[1].info.summary : false).toBe(true) expect(filtered.map((msg) => msg.info.id)).not.toContain(large.id) + expect(filtered.map((msg) => msg.info.id)).not.toContain(keep.id) }).pipe(withCompaction({ llm: stub.layer, config: cfg({ tail_turns: 1, preserve_recent_tokens: 100 }) })) }, { git: true }, @@ -1353,13 +1354,13 @@ describe("session.compaction.process", () => { ) itCompaction.instance( - "summarizes only the head while keeping recent tail out of summary input", + "summarizes the head while serializing recent tail into summary input", () => { const stub = llm() - let captured = "" + let captured: LLM.StreamInput["messages"] = [] stub.push( reply("summary", (input) => { - captured = JSON.stringify(input.messages) + captured = input.messages }), ) return Effect.gen(function* () { @@ -1380,10 +1381,15 @@ describe("session.compaction.process", () => { auto: false, }) - expect(captured).toContain("older context") - expect(captured).not.toContain("keep this turn") - expect(captured).not.toContain("and this one too") - expect(captured).not.toContain("What did we do so far?") + const head = JSON.stringify(captured.slice(0, -1)) + const prompt = JSON.stringify(captured.at(-1)) + expect(head).toContain("older context") + expect(head).not.toContain("keep this turn") + expect(head).not.toContain("and this one too") + expect(prompt).toContain("keep this turn") + expect(prompt).toContain("and this one too") + expect(prompt).toContain("recent-conversation-tail") + expect(prompt).not.toContain("What did we do so far?") }).pipe(withCompaction({ llm: stub.layer })) }, { git: true }, @@ -1431,7 +1437,7 @@ describe("session.compaction.process", () => { { git: true }, ) - itCompaction.instance("keeps recent pre-compaction turns across repeated compactions", () => { + itCompaction.instance("does not replay recent pre-compaction turns across repeated compactions", () => { const stub = llm() stub.push(reply("summary one")) stub.push(reply("summary two")) @@ -1462,8 +1468,8 @@ describe("session.compaction.process", () => { expect(ids).not.toContain(u1.id) expect(ids).not.toContain(u2.id) - expect(ids).toContain(u3.id) - expect(ids).toContain(u4.id) + expect(ids).not.toContain(u3.id) + expect(ids).not.toContain(u4.id) expect(filtered.some((msg) => msg.info.role === "assistant" && msg.info.summary)).toBe(true) expect( filtered.some((msg) => msg.info.role === "user" && msg.parts.some((part) => part.type === "compaction")), @@ -1472,7 +1478,7 @@ describe("session.compaction.process", () => { }) itCompaction.instance( - "ignores previous summaries when sizing the retained tail", + "ignores previous summaries when sizing the serialized tail", Effect.gen(function* () { const ssn = yield* SessionNs.Service const test = yield* TestInstance @@ -1511,7 +1517,7 @@ describe("session.compaction.process", () => { const part = yield* readCompactionPart(session.id) expect(part?.type).toBe("compaction") - expect(part?.tail_start_id).toBe(keep.id) + expect(part?.tail_start_id).toBeUndefined() }).pipe(withCompaction({ config: cfg({ tail_turns: 2, preserve_recent_tokens: 500 }) })), ) }) diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index 05ec2bad4..86e1d85d0 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -785,7 +785,7 @@ describe("MessageV2.filterCompacted", () => { }) }) - test("retains original tail when compaction stores tail_start_id", async () => { + test("ignores original tail when compaction stores tail_start_id", async () => { await WithInstance.provide({ directory: root, fn: async () => { @@ -834,14 +834,14 @@ describe("MessageV2.filterCompacted", () => { const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(result.map((item) => item.info.id)).toEqual([c1, s1, u2, a2, u3, a3]) + expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) await svc.remove(session.id) }, }) }) - test("fork remaps compaction tail_start_id for filterCompacted", async () => { + test("fork keeps legacy tail_start_id without replaying the tail", async () => { await WithInstance.provide({ directory: root, fn: async () => { @@ -889,7 +889,7 @@ describe("MessageV2.filterCompacted", () => { }) const parentFiltered = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(parentFiltered.map((item) => item.info.id)).toEqual([c1, s1, u2, a2, u3, a3]) + expect(parentFiltered.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) const forked = await svc.fork({ sessionID: session.id }) const childFiltered = MessageV2.filterCompacted(MessageV2.stream(forked.id)) @@ -899,7 +899,7 @@ describe("MessageV2.filterCompacted", () => { expect(tailPart?.type).toBe("compaction") if (!tailPart || tailPart.type !== "compaction") throw new Error("Expected forked compaction part") expect(tailPart.tail_start_id).toBeDefined() - expect(childFiltered.some((m) => m.info.id === tailPart.tail_start_id)).toBe(true) + expect(childFiltered.some((m) => m.info.id === tailPart.tail_start_id)).toBe(false) await svc.remove(forked.id) await svc.remove(session.id) @@ -907,7 +907,7 @@ describe("MessageV2.filterCompacted", () => { }) }) - test("retains an assistant tail when compaction starts inside a turn", async () => { + test("does not replay an assistant tail when compaction starts inside a turn", async () => { await WithInstance.provide({ directory: root, fn: async () => { @@ -964,7 +964,7 @@ describe("MessageV2.filterCompacted", () => { const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(result.map((item) => item.info.id)).toEqual([c1, s1, a3, u3, a4]) + expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a4]) await svc.remove(session.id) }, @@ -1041,7 +1041,7 @@ describe("MessageV2.filterCompacted", () => { const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(result.map((item) => item.info.id)).toEqual([c2, s2, u3, a3, u4, a4]) + expect(result.map((item) => item.info.id)).toEqual([c2, s2, u4, a4]) await svc.remove(session.id) }, From c933504d9c7074104d74ee1e15fa3b09d1afa84e Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Mon, 11 May 2026 17:21:59 +0800 Subject: [PATCH 014/378] fix(ui): better handle patch file support when rendering patch/edit tools (#26828) --- packages/ui/src/components/message-part.tsx | 46 +++++++++++++++------ packages/ui/src/components/session-diff.ts | 18 +++++--- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 7a7d5b15f..35598a170 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -264,6 +264,7 @@ function getDirectory(path: string | undefined) { } import type { IconProps } from "./icon" +import { normalize } from "./session-diff" export type ToolInfo = { icon: IconProps["name"] @@ -1878,6 +1879,31 @@ ToolRegistry.register({ const path = createMemo(() => props.metadata?.filediff?.file || props.input.filePath || "") const filename = () => getFilename(props.input.filePath ?? "") const pending = () => props.status === "pending" || props.status === "running" + + const fileCompProps = createMemo(() => { + try { + if (props.metadata?.filediff) { + const diff = normalize({ + ...props.metadata?.filediff, + status: "modified", + }) + const fileDiff = diff.fileDiff + if (fileDiff) return { fileDiff, hunkSeparators: fileDiff.isPartial ? "simple" : "line-info-basic" } + } + } catch {} + + return { + before: { + name: props.metadata?.filediff?.file || props.input.filePath, + contents: props.metadata?.filediff?.before || props.input.oldString || "", + }, + after: { + name: props.metadata?.filediff?.file || props.input.filePath, + contents: props.metadata?.filediff?.after || props.input.newString || "", + }, + } + }) + return (
- +
@@ -2111,7 +2126,12 @@ ToolRegistry.register({
- +
diff --git a/packages/ui/src/components/session-diff.ts b/packages/ui/src/components/session-diff.ts index 60dcffd83..52ef0a4bb 100644 --- a/packages/ui/src/components/session-diff.ts +++ b/packages/ui/src/components/session-diff.ts @@ -1,4 +1,4 @@ -import { parseDiffFromFile, type FileDiffMetadata } from "@pierre/diffs" +import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs" import { formatPatch, parsePatch, structuredPatch } from "diff" import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" @@ -34,6 +34,8 @@ function patch(diff: ReviewDiff) { const afterLines: Array<{ text: string; newline: boolean }> = [] let previous: "-" | "+" | " " | undefined + const patchIsPartial = patch.hunks.every((h) => h.oldStart > 1) + for (const hunk of patch.hunks) { for (const line of hunk.lines) { if (line.startsWith("\\")) { @@ -67,9 +69,10 @@ function patch(diff: ReviewDiff) { before: beforeLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""), after: afterLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""), patch: diff.patch, + patchIsPartial, } } catch { - return { before: "", after: "", patch: diff.patch } + return { before: "", after: "", patch: diff.patch, patchIsPartial: false } } } return { @@ -86,27 +89,32 @@ function patch(diff: ReviewDiff) { { context: Number.MAX_SAFE_INTEGER }, ), ), + patchIsPartial: false, } } -function file(file: string, patch: string, before: string, after: string) { +function file(file: string, patch: string, before: string, after: string, partial = false) { const hit = cache.get(patch) if (hit) return hit - const value = parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after }) + let value: FileDiffMetadata | undefined + if (partial) value = parsePatchFiles(patch)[0]?.files[0] + if (value === undefined) value = parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after }) + cache.set(patch, value) return value } export function normalize(diff: ReviewDiff): ViewDiff { const next = patch(diff) + const fileDiff = file(diff.file, next.patch, next.before, next.after, next.patchIsPartial) return { file: diff.file, patch: next.patch, additions: diff.additions, deletions: diff.deletions, status: diff.status, - fileDiff: file(diff.file, next.patch, next.before, next.after), + fileDiff, } } From 8874d4a42ff13704ac50124ceda328eccc283253 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 11 May 2026 09:37:14 -0400 Subject: [PATCH 015/378] zen: deekseek v4 flash free --- packages/web/src/content/docs/ar/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/bs/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/da/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/de/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/es/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/fr/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/it/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/nb/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/pl/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/pt-br/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/ru/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/th/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/tr/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/zen.mdx | 88 +++++++++++---------- packages/web/src/content/docs/zh-cn/zen.mdx | 86 ++++++++++---------- packages/web/src/content/docs/zh-tw/zen.mdx | 88 +++++++++++---------- 16 files changed, 735 insertions(+), 671 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index 748384c21..4f5dd6b45 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -57,48 +57,49 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن يمكنك أيضا الوصول إلى نماذجنا عبر نقاط نهاية API التالية. -| النموذج | معرّف النموذج | نقطة النهاية | حزمة AI SDK | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| النموذج | معرّف النموذج | نقطة النهاية | حزمة AI SDK | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/`. على سبيل المثال، بالنسبة إلى GPT 5.5، ستستخدم `opencode/gpt-5.5` في إعداداتك. @@ -121,6 +122,7 @@ https://opencode.ai/zen/v1/models | النموذج | الإدخال | الإخراج | القراءة المخزنة | الكتابة المخزنة | | --------------------------------- | ------- | ------- | --------------- | --------------- | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -173,6 +175,7 @@ https://opencode.ai/zen/v1/models النماذج المجانية: +- DeepSeek V4 Flash Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - MiniMax M2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Ring 2.6 1T Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Nemotron 3 Super Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. @@ -225,6 +228,7 @@ https://opencode.ai/zen/v1/models تتم استضافة جميع نماذجنا في الولايات المتحدة. يلتزم مزوّدونا بسياسة عدم الاحتفاظ بالبيانات ولا يستخدمون بياناتك لتدريب النماذج، مع الاستثناءات التالية: - Big Pickle: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. +- DeepSeek V4 Flash Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - MiniMax M2.5 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Ring 2.6 1T Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Nemotron 3 Super Free (نقاط نهاية NVIDIA المجانية): يُقدَّم بموجب [شروط خدمة النسخة التجريبية من واجهة NVIDIA API](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). للاستخدام التجريبي فقط، وليس للإنتاج أو البيانات الحساسة. تقوم NVIDIA بتسجيل المطالبات والمخرجات لتحسين نماذجها وخدماتها. لا ترسل بيانات شخصية أو سرية. diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 22299b12c..ec2ab1e60 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -62,48 +62,49 @@ Naplata se vrši po zahtjevu i možete dodavati kredit na svoj račun. Našim modelima možete pristupiti i preko sljedećih API endpointa. -| Model | Model ID | Endpoint | AI SDK Package | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Model | Model ID | Endpoint | AI SDK Package | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | [model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format `opencode/`. Na primjer, za GPT 5.5 u konfiguraciji biste @@ -128,6 +129,7 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -180,6 +182,7 @@ Naknade za kreditne kartice prosljeđujemo po stvarnom trošku (4.4% + $0.30 po Besplatni modeli: +- DeepSeek V4 Flash Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - MiniMax M2.5 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Ring 2.6 1T Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Nemotron 3 Super Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. @@ -237,6 +240,7 @@ Svi naši modeli su hostovani u US. Naši provajderi prate zero-retention politi i ne koriste vaše podatke za treniranje modela, uz sljedeće izuzetke: - Big Pickle: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. +- DeepSeek V4 Flash Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - MiniMax M2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Ring 2.6 1T Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Nemotron 3 Super Free (besplatni NVIDIA endpointi): Dostupan je prema [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Samo za probnu upotrebu, nije za produkciju niti osjetljive podatke. NVIDIA bilježi promptove i izlaze radi poboljšanja svojih modela i usluga. Nemojte slati lične ili povjerljive podatke. diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 0fb2c9737..dec96852e 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -62,48 +62,49 @@ Du bliver opkrævet pr. anmodning, og du kan tilføje kredit til din konto. Du kan også få adgang til vores modeller gennem følgende API-endpoints. -| Model | Model ID | Endpoint | AI SDK-pakke | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Model | Model ID | Endpoint | AI SDK-pakke | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | [model id](/docs/config/#models) i din OpenCode-konfiguration bruger formatet `opencode/`. For eksempel ville du for GPT 5.5 @@ -128,6 +129,7 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -180,6 +182,7 @@ Kreditkortgebyrer videregives til kostpris (4.4% + $0.30 pr. transaktion); vi op De gratis modeller: +- DeepSeek V4 Flash Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - MiniMax M2.5 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Ring 2.6 1T Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Nemotron 3 Super Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. @@ -235,6 +238,7 @@ at opkræve dig mere end $20, hvis din saldo kommer under $5. Alle vores modeller hostes i US. Vores udbydere følger en nul-opbevaringspolitik og bruger ikke dine data til modeltræning, med følgende undtagelser: - Big Pickle: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. +- DeepSeek V4 Flash Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - MiniMax M2.5 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Ring 2.6 1T Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endpoints): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun til prøvebrug, ikke til produktion eller følsomme data. Prompts og outputs logges af NVIDIA for at forbedre deres modeller og tjenester. Indsend ikke personlige eller fortrolige data. diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 425d9b651..297481ee6 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -53,48 +53,49 @@ Dir wird pro Anfrage berechnet, und du kannst deinem Konto Guthaben hinzufügen. Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. -| Model | Model ID | Endpoint | AI SDK Package | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Model | Model ID | Endpoint | AI SDK Package | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/`. Für GPT 5.5 würdest du zum Beispiel `opencode/gpt-5.5` in deiner Konfiguration verwenden. @@ -117,6 +118,7 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -169,6 +171,7 @@ Kreditkartengebühren werden zum Selbstkostenpreis weitergegeben (4.4% + $0.30 p Die kostenlosen Modelle: +- DeepSeek V4 Flash Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - MiniMax M2.5 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Ring 2.6 1T Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Nemotron 3 Super Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. @@ -221,6 +224,7 @@ Angenommen, du setzt ein monatliches Nutzungslimit von $20, dann wird Zen in ein Alle unsere Modelle werden in den USA gehostet. Unsere Provider folgen einer Zero-Retention-Richtlinie und verwenden deine Daten nicht zum Trainieren von Modellen, mit den folgenden Ausnahmen: - Big Pickle: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. +- DeepSeek V4 Flash Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - MiniMax M2.5 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Ring 2.6 1T Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Nemotron 3 Super Free (kostenlose NVIDIA-Endpunkte): Bereitgestellt gemäß den [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Nur für Testzwecke, nicht für Produktion oder sensible Daten. Eingaben und Ausgaben werden von NVIDIA protokolliert, um seine Modelle und Dienste zu verbessern. Übermitteln Sie keine personenbezogenen oder vertraulichen Daten. diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 8e0e51293..9d4a88c24 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -62,48 +62,49 @@ Se te cobra por solicitud y puedes agregar créditos a tu cuenta. También puedes acceder a nuestros modelos a través de los siguientes endpoints de API. -| Modelo | Model ID | Endpoint | AI SDK Package | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Modelo | Model ID | Endpoint | AI SDK Package | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | El [identificador del modelo](/docs/config/#models) en tu configuración de OpenCode usa el formato `opencode/`. Por ejemplo, para GPT 5.5, usarías @@ -128,6 +129,7 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Modelo | Entrada | Salida | Lectura en caché | Escritura en caché | | --------------------------------- | ------- | ------- | ---------------- | ------------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -180,6 +182,7 @@ Las comisiones de tarjeta de crédito se trasladan al costo (4.4% + $0.30 por tr Los modelos gratuitos: +- DeepSeek V4 Flash Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - MiniMax M2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Ring 2.6 1T Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Nemotron 3 Super Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. @@ -235,6 +238,7 @@ cobrándote más de $20 si tu saldo baja de $5. Todos nuestros modelos están alojados en US. Nuestros proveedores siguen una política de zero-retention y no usan tus datos para el entrenamiento de modelos, con las siguientes excepciones: - Big Pickle: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. +- DeepSeek V4 Flash Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - MiniMax M2.5 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Ring 2.6 1T Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Nemotron 3 Super Free (endpoints gratuitos de NVIDIA): Se ofrece bajo los [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo para uso de prueba, no para producción ni datos sensibles. NVIDIA registra los prompts y las salidas para mejorar sus modelos y servicios. No envíes datos personales ni confidenciales. diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 0d07ee314..8b9959753 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -53,48 +53,49 @@ La facturation se fait à la requête et vous pouvez ajouter des crédits à vot Vous pouvez également accéder à nos modèles via les points de terminaison API suivants. -| Modèle | ID du modèle | Point de terminaison | Package AI SDK | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Modèle | ID du modèle | Point de terminaison | Package AI SDK | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.5, vous utiliseriez `opencode/gpt-5.5` dans votre configuration. @@ -117,6 +118,7 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Modèle | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -169,6 +171,7 @@ Les frais de carte de crédit sont répercutés au prix coûtant (4.4% + $0.30 p Les modèles gratuits : +- DeepSeek V4 Flash Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - MiniMax M2.5 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Ring 2.6 1T Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Nemotron 3 Super Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. @@ -221,6 +224,7 @@ Par exemple, si vous définissez une limite d'utilisation mensuelle à $20, Zen Tous nos modèles sont hébergés aux US. Nos fournisseurs suivent une politique de rétention zéro et n'utilisent pas vos données pour l'entraînement des modèles, avec les exceptions suivantes : - Big Pickle : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. +- DeepSeek V4 Flash Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - MiniMax M2.5 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Ring 2.6 1T Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Nemotron 3 Super Free (endpoints NVIDIA gratuits) : Fourni dans le cadre des [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Réservé à un usage d'essai, pas à la production ni aux données sensibles. Les prompts et les sorties sont journalisés par NVIDIA pour améliorer ses modèles et services. N'envoyez pas de données personnelles ou confidentielles. diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index df24babb2..83adc90b4 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -62,48 +62,49 @@ Ti viene addebitato ogni richiesta e puoi aggiungere credito al tuo account. Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. -| Modello | Model ID | Endpoint | Pacchetto AI SDK | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Modello | Model ID | Endpoint | Pacchetto AI SDK | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | Il [model id](/docs/config/#models) nella config di OpenCode usa il formato `opencode/`. Per esempio, per GPT 5.5, useresti @@ -128,6 +129,7 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Modello | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -180,6 +182,7 @@ Le commissioni della carta di credito vengono trasferite al costo (4.4% + $0.30 I modelli gratuiti: +- DeepSeek V4 Flash Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - MiniMax M2.5 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Ring 2.6 1T Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Nemotron 3 Super Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. @@ -235,6 +238,7 @@ per addebitarti più di $20 se il tuo saldo scende sotto $5. Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una policy di zero-retention e non usano i tuoi dati per l'addestramento dei modelli, con le seguenti eccezioni: - Big Pickle: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. +- DeepSeek V4 Flash Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - MiniMax M2.5 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Ring 2.6 1T Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Nemotron 3 Super Free (endpoint NVIDIA gratuiti): fornito secondo i [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo per uso di prova, non per produzione o dati sensibili. NVIDIA registra prompt e output per migliorare i propri modelli e servizi. Non inviare dati personali o riservati. diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index c5726f4b4..3f7faa80d 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -62,48 +62,49 @@ Du blir belastet per forespørsel, og du kan legge til kreditt på kontoen din. Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. -| Modell | Modell-ID | Endepunkt | AI SDK-pakke | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Modell | Modell-ID | Endepunkt | AI SDK-pakke | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | [modell-id](/docs/config/#models) i OpenCode-konfigurasjonen din bruker formatet `opencode/`. For eksempel, for GPT 5.5, ville du @@ -128,6 +129,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Modell | Inndata | Utdata | Bufret lesing | Bufret skriving | | --------------------------------- | ------- | ------- | ------------- | --------------- | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -180,6 +182,7 @@ Kredittkortgebyrer videreføres til kostpris (4.4% + $0.30 per transaction); vi Gratis-modellene: +- DeepSeek V4 Flash Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - MiniMax M2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Ring 2.6 1T Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Nemotron 3 Super Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. @@ -235,6 +238,7 @@ med å belaste deg mer enn $20 hvis saldoen din går under $5. Alle modellene våre hostes i US. Leverandørene våre følger en policy for zero-retention og bruker ikke dataene dine til modelltrening, med følgende unntak: - Big Pickle: I gratisperioden kan innsamlede data brukes til å forbedre modellen. +- DeepSeek V4 Flash Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - MiniMax M2.5 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Ring 2.6 1T Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endepunkter): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun for prøvebruk, ikke for produksjon eller sensitive data. Prompter og svar logges av NVIDIA for å forbedre modellene og tjenestene deres. Ikke send inn personopplysninger eller konfidensielle data. diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index f22d10b3a..e989ac49d 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -62,48 +62,49 @@ Płatność jest naliczana za każde żądanie i możesz doładować swoje konto Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. -| Model | ID modelu | Endpoint | Pakiet AI SDK | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Model | ID modelu | Endpoint | Pakiet AI SDK | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu `opencode/`. Na przykład dla GPT 5.5 użyjesz w konfiguracji @@ -128,6 +129,7 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Model | Wejście | Wyjście | Odczyt z cache | Zapis do cache | | --------------------------------- | ------- | ------- | -------------- | -------------- | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -181,6 +183,7 @@ Opłaty za karty kredytowe są przenoszone po kosztach (4.4% + $0.30 per transac Darmowe modele: +- DeepSeek V4 Flash Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - MiniMax M2.5 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Ring 2.6 1T Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Nemotron 3 Super Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. @@ -236,6 +239,7 @@ ostatecznie obciążyć Cię kwotą wyższą niż $20, jeśli Twoje saldo spadni Wszystkie nasze modele są hostowane w US. Nasi dostawcy stosują politykę zero-retention i nie wykorzystują Twoich danych do trenowania modeli, z następującymi wyjątkami: - Big Pickle: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. +- DeepSeek V4 Flash Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - MiniMax M2.5 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Ring 2.6 1T Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Nemotron 3 Super Free (darmowe endpointy NVIDIA): Udostępniany zgodnie z [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Tylko do użytku próbnego, nie do produkcji ani danych wrażliwych. NVIDIA rejestruje prompty i odpowiedzi, aby ulepszać swoje modele i usługi. Nie przesyłaj danych osobowych ani poufnych. diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 55678781a..00cc3a3a4 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -53,48 +53,49 @@ Você é cobrado por solicitação e pode adicionar créditos à sua conta. Você também pode acessar nossos modelos pelos seguintes endpoints de API. -| Modelo | ID do modelo | Endpoint | Pacote AI SDK | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Modelo | ID do modelo | Endpoint | Pacote AI SDK | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.5, você usaria `opencode/gpt-5.5` na sua configuração. @@ -117,6 +118,7 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Modelo | Entrada | Saída | Leitura em cache | Escrita em cache | | --------------------------------- | ------- | ------- | ---------------- | ---------------- | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -169,6 +171,7 @@ As taxas de cartão de crédito são repassadas a preço de custo (4.4% + $0.30 Os modelos gratuitos: +- DeepSeek V4 Flash Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - MiniMax M2.5 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Ring 2.6 1T Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Nemotron 3 Super Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. @@ -221,6 +224,7 @@ Por exemplo, digamos que você defina um limite mensal de uso de $20; o Zen não Todos os nossos modelos são hospedados nos US. Nossos provedores seguem uma política de zero-retention e não usam seus dados para treinamento de modelos, com as seguintes exceções: - Big Pickle: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. +- DeepSeek V4 Flash Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - MiniMax M2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Ring 2.6 1T Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Nemotron 3 Super Free (endpoints gratuitos da NVIDIA): Fornecido sob os [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Apenas para uso de avaliação, não para produção nem dados sensíveis. A NVIDIA registra prompts e saídas para melhorar seus modelos e serviços. Não envie dados pessoais ou confidenciais. diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index c685d2dac..5a3485f98 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -62,48 +62,49 @@ OpenCode Zen работает как любой другой провайдер Вы также можете получить доступ к нашим моделям через следующие конечные точки API. -| Модель | Идентификатор модели | Конечная точка | Пакет AI SDK | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Модель | Идентификатор модели | Конечная точка | Пакет AI SDK | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | [идентификатор модели](/docs/config/#models) в вашей конфигурации OpenCode использует формат `opencode/`. Например, для GPT 5.5 вам нужно @@ -128,6 +129,7 @@ https://opencode.ai/zen/v1/models | Модель | Вход | Выход | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -180,6 +182,7 @@ https://opencode.ai/zen/v1/models Бесплатные модели: +- DeepSeek V4 Flash Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - MiniMax M2.5 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Ring 2.6 1T Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Nemotron 3 Super Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. @@ -235,6 +238,7 @@ https://opencode.ai/zen/v1/models Все наши модели размещены в US. Наши провайдеры придерживаются политики нулевого хранения и не используют ваши данные для обучения моделей, за следующими исключениями: - Big Pickle: во время бесплатного периода собранные данные могут использоваться для улучшения модели. +- DeepSeek V4 Flash Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - MiniMax M2.5 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Ring 2.6 1T Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Nemotron 3 Super Free (бесплатные эндпоинты NVIDIA): предоставляется в соответствии с [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Только для пробного использования, не для продакшена и не для чувствительных данных. NVIDIA логирует запросы и ответы, чтобы улучшать свои модели и сервисы. Не отправляйте персональные или конфиденциальные данные. diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 68139d962..6006baada 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -55,48 +55,49 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน คุณยังสามารถเข้าถึงโมเดลของเราผ่าน API endpoints ต่อไปนี้ได้ -| Model | Model ID | Endpoint | AI SDK Package | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Model | Model ID | Endpoint | AI SDK Package | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | [model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/` ตัวอย่างเช่น สำหรับ GPT 5.5 คุณจะใช้ `opencode/gpt-5.5` ใน config ของคุณ @@ -119,6 +120,7 @@ https://opencode.ai/zen/v1/models | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -171,6 +173,7 @@ https://opencode.ai/zen/v1/models โมเดลฟรี: +- DeepSeek V4 Flash Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - MiniMax M2.5 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Ring 2.6 1T Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Nemotron 3 Super Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล @@ -223,6 +226,7 @@ https://opencode.ai/zen/v1/models โมเดลทั้งหมดของเราโฮสต์อยู่ใน US provider ของเราปฏิบัติตามนโยบาย zero-retention และจะไม่ใช้ข้อมูลของคุณเพื่อฝึกโมเดล ยกเว้นกรณีต่อไปนี้: - Big Pickle: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล +- DeepSeek V4 Flash Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - MiniMax M2.5 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Ring 2.6 1T Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Nemotron 3 Super Free (endpoint ฟรีของ NVIDIA): ให้บริการภายใต้ [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) ใช้สำหรับการทดลองเท่านั้น ไม่เหมาะสำหรับ production หรือข้อมูลที่อ่อนไหว NVIDIA จะบันทึก prompt และ output เพื่อนำไปปรับปรุงโมเดลและบริการของตน โปรดอย่าส่งข้อมูลส่วนบุคคลหรือข้อมูลลับ. diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 5d4ed76ad..86cdd8489 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -53,48 +53,49 @@ OpenCode Zen, OpenCode'daki diğer sağlayıcılar gibi çalışır. Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsiniz. -| Model | Model ID | Endpoint | AI SDK Package | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Model | Model ID | Endpoint | AI SDK Package | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.5 için yapılandırmanızda `opencode/gpt-5.5` kullanırsınız. @@ -117,6 +118,7 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -169,6 +171,7 @@ Kredi kartı ücretleri maliyet üzerinden yansıtılır (%4.4 + işlem başına Ücretsiz modeller: +- DeepSeek V4 Flash Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - MiniMax M2.5 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Ring 2.6 1T Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Nemotron 3 Super Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. @@ -221,6 +224,7 @@ Ayrıca tüm çalışma alanı için ve ekibinizdeki her üye için aylık kulla Tüm modellerimiz US'de barındırılıyor. Sağlayıcılarımız zero-retention politikası uygular ve aşağıdaki istisnalar dışında verilerinizi model eğitimi için kullanmaz: - Big Pickle: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. +- DeepSeek V4 Flash Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - MiniMax M2.5 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Ring 2.6 1T Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Nemotron 3 Super Free (ücretsiz NVIDIA uç noktaları): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) kapsamında sunulur. Yalnızca deneme amaçlıdır; üretim veya hassas veriler için uygun değildir. NVIDIA, modellerini ve hizmetlerini geliştirmek için promptları ve çıktıları kaydeder. Kişisel veya gizli veri göndermeyin. diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 2d180b30b..363bc6cc8 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -62,48 +62,49 @@ You are charged per request and you can add credits to your account. You can also access our models through the following API endpoints. -| Model | Model ID | Endpoint | AI SDK Package | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Model | Model ID | Endpoint | AI SDK Package | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | The [model id](/docs/config/#models) in your OpenCode config uses the format `opencode/`. For example, for GPT 5.5, you would @@ -128,6 +129,7 @@ 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 | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -180,6 +182,7 @@ Credit card fees are passed along at cost (4.4% + $0.30 per transaction); we don The free models: +- DeepSeek V4 Flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - 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. - Ring 2.6 1T 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. @@ -235,6 +238,7 @@ charging you more than $20 if your balance goes below $5. All our models are hosted in the US. Our providers follow a zero-retention policy and do not use your data for model training, with the following exceptions: - Big Pickle: During its free period, collected data may be used to improve the model. +- DeepSeek V4 Flash Free: 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. - Ring 2.6 1T Free: During its free period, collected data may be used to improve the model. - Nemotron 3 Super Free (NVIDIA free endpoints): Provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Trial use only — not for production or sensitive data. Prompts and outputs are logged by NVIDIA to improve its models and services. Do not submit personal or confidential data. diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index e81e51a67..7fa82de43 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -54,47 +54,48 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 你也可以通过以下 API 端点访问我们的模型。 | 模型 | 模型 ID | 端点 | AI SDK 包 | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | 在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/` 格式。例如,对于 GPT 5.5,你需要在配置中使用 `opencode/gpt-5.5`。 @@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models | 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 | | --------------------------------- | ------ | ------- | -------- | -------- | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -169,6 +171,7 @@ https://opencode.ai/zen/v1/models 免费模型: +- DeepSeek V4 Flash Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - MiniMax M2.5 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Ring 2.6 1T Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Nemotron 3 Super Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 @@ -221,6 +224,7 @@ https://opencode.ai/zen/v1/models 我们所有模型都托管在 US。我们的提供商遵循零保留政策,不会将你的数据用于模型训练,但以下情况除外: - Big Pickle:在免费期间,收集的数据可能会被用于改进模型。 +- DeepSeek V4 Flash Free:在免费期间,收集的数据可能会被用于改进模型。 - MiniMax M2.5 Free:在免费期间,收集的数据可能会被用于改进模型。 - Ring 2.6 1T Free:在免费期间,收集的数据可能会被用于改进模型。 - Nemotron 3 Super Free(NVIDIA 免费端点):根据 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。仅供试用,不适用于生产环境或敏感数据。NVIDIA 会记录提示词和输出内容,以改进其模型和服务。请勿提交个人或机密数据。 diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 163454301..7a6e2315f 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -57,48 +57,49 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 你也可以透過以下 API 端點存取我們的模型。 -| 模型 | Model ID | 端點 | AI SDK Package | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| 模型 | Model ID | 端點 | AI SDK Package | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | OpenCode 設定中的 [模型 ID](/docs/config/#models) 會使用 `opencode/` 格式。例如,如果是 GPT 5.5,你會在設定中使用 `opencode/gpt-5.5`。 @@ -122,6 +123,7 @@ https://opencode.ai/zen/v1/models | 模型 | 輸入 | 輸出 | 快取讀取 | 快取寫入 | | --------------------------------- | ------ | ------- | -------- | -------- | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -175,6 +177,7 @@ https://opencode.ai/zen/v1/models 免費模型: +- DeepSeek V4 Flash Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - MiniMax M2.5 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Ring 2.6 1T Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Nemotron 3 Super Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 @@ -228,6 +231,7 @@ https://opencode.ai/zen/v1/models 我們所有模型都託管於美國。我們的供應商遵循零保留政策,且不會將你的資料用於模型訓練,但以下情況除外: - Big Pickle: 在免費期間,收集到的資料可能會用於改進模型。 +- DeepSeek V4 Flash Free: 在免費期間,收集到的資料可能會用於改進模型。 - MiniMax M2.5 Free: 在免費期間,收集到的資料可能會用於改進模型。 - Ring 2.6 1T Free: 在免費期間,收集到的資料可能會用於改進模型。 - Nemotron 3 Super Free(NVIDIA 免費端點):依據 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。僅供試用,不適用於正式環境或敏感資料。NVIDIA 會記錄提示詞與輸出內容,以改進其模型與服務。請勿提交個人或機密資料。 From e93b1f3a7f52fa00da3a2139ff1d21bff1723486 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 13:38:44 +0000 Subject: [PATCH 016/378] chore: generate --- packages/web/src/content/docs/zh-cn/zen.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 7fa82de43..442bde20e 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -53,7 +53,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 你也可以通过以下 API 端点访问我们的模型。 -| 模型 | 模型 ID | 端点 | AI SDK 包 | +| 模型 | 模型 ID | 端点 | AI SDK 包 | | ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | | GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | From 1eff01b6a584fa095dd9063377307f5d73eb8553 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 11 May 2026 09:43:10 -0400 Subject: [PATCH 017/378] sync --- packages/web/src/content/docs/ja/zen.mdx | 88 +++++++++++++----------- packages/web/src/content/docs/ko/zen.mdx | 88 +++++++++++++----------- 2 files changed, 92 insertions(+), 84 deletions(-) diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 05d6c8b81..e45c162a3 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -53,48 +53,49 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 以下の API エンドポイントを通じて、私たちのモデルにアクセスすることもできます。 -| Model | Model ID | Endpoint | AI SDK Package | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| Model | Model ID | Endpoint | AI SDK Package | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/` 形式です。たとえば、GPT 5.5 では設定に `opencode/gpt-5.5` を使用します。 @@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -169,6 +171,7 @@ https://opencode.ai/zen/v1/models 無料モデル: +- DeepSeek V4 Flash Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - MiniMax M2.5 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Ring 2.6 1T Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Nemotron 3 Super Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 @@ -221,6 +224,7 @@ https://opencode.ai/zen/v1/models すべてのモデルは米国でホストされています。私たちのプロバイダーはゼロ保持ポリシーに従っており、以下の例外を除き、モデル学習にあなたのデータを使用しません。 - Big Pickle: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 +- DeepSeek V4 Flash Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - MiniMax M2.5 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Ring 2.6 1T Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Nemotron 3 Super Free(NVIDIA の無料エンドポイント): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) に基づいて提供されます。試用専用であり、本番環境や機密性の高いデータには使用しないでください。プロンプトと出力は、NVIDIA が自社のモデルとサービスを改善するために記録します。個人情報や機密データは送信しないでください。 diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 513093a2c..87fb4aa8e 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -53,48 +53,49 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. 다음 API 엔드포인트를 통해서도 모델에 접근할 수 있습니다. -| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | -| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| 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` | -| Kimi K2.6 | kimi-k2.6 | `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` | -| Ring 2.6 1T | ring-2.6-1t-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` | +| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | +| ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 Nano | gpt-5.4-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.7 | claude-opus-4-7 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 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` | +| Kimi K2.6 | kimi-k2.6 | `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` | +| DeepSeek V4 Flash Free | deepseek-v4-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ring 2.6 1T | ring-2.6-1t-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` | OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식입니다. 예를 들어 GPT 5.5를 사용하려면 config에서 `opencode/gpt-5.5`를 사용하면 됩니다. @@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models | 모델 | 입력 | 출력 | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| DeepSeek V4 Flash Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ring 2.6 1T Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | @@ -169,6 +171,7 @@ https://opencode.ai/zen/v1/models 무료 모델: +- DeepSeek V4 Flash Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - MiniMax M2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Ring 2.6 1T Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Nemotron 3 Super Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. @@ -221,6 +224,7 @@ https://opencode.ai/zen/v1/models 모든 모델은 미국에서 호스팅됩니다. provider는 zero-retention 정책을 따르며, 다음 예외를 제외하고는 데이터를 모델 학습에 사용하지 않습니다. - Big Pickle: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. +- DeepSeek V4 Flash Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - MiniMax M2.5 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Ring 2.6 1T Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Nemotron 3 Super Free(NVIDIA 무료 엔드포인트): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)에 따라 제공됩니다. 평가판 전용이며 프로덕션 환경이나 민감한 데이터에는 사용할 수 없습니다. NVIDIA는 자사 모델과 서비스를 개선하기 위해 프롬프트와 출력을 기록합니다. 개인 정보나 기밀 데이터는 제출하지 마세요. From fca89e5e4dd14226c4b754796573e99e507631b9 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 13:44:39 +0000 Subject: [PATCH 018/378] chore: generate --- packages/web/src/content/docs/ko/zen.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 87fb4aa8e..6af8cf14b 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -53,7 +53,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. 다음 API 엔드포인트를 통해서도 모델에 접근할 수 있습니다. -| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | +| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | | ---------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | | GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | From 6c9f12bb2dee10a747218a39fb4dd94eff6a34ef Mon Sep 17 00:00:00 2001 From: Victor Navarro Date: Mon, 11 May 2026 16:04:54 +0200 Subject: [PATCH 019/378] chore: exclude status 429 from free model alerts (#26879) --- infra/monitoring.ts | 62 +++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/infra/monitoring.ts b/infra/monitoring.ts index b4ece54be..c08d39f26 100644 --- a/infra/monitoring.ts +++ b/infra/monitoring.ts @@ -1,6 +1,8 @@ import { SECRET } from "./secret" import { domain } from "./stage" +const description = "Managed by SST (Don't edit in Honeycomb UI)" + const webhookRecipient = new honeycomb.WebhookRecipient("DiscordAlerts", { name: $app.stage === "production" ? "Discord Alerts" : `Discord Alerts (${$app.stage})`, url: `https://${domain}/honeycomb/webhook`, @@ -25,6 +27,16 @@ const webhookRecipient = new honeycomb.WebhookRecipient("DiscordAlerts", { ], }) +// Honeycomb can keep stale query-local calculated fields when the name is unchanged, +// so tie the field name to the expression while avoiding deploy-to-deploy churn. +// https://github.com/honeycombio/terraform-provider-honeycombio/issues/852 +const calculatedField = (field: { name: string; expression: string }) => ({ + ...field, + name: `${field.name}_${( + Array.from(field.expression).reduce((result, char) => Math.imul(31, result) + char.charCodeAt(0), 0) >>> 0 + ).toString(36)}`, +}) + const modelHttpErrorsQuery = (product: "go" | "zen") => { const filters = [ { column: "model", op: "exists" }, @@ -32,21 +44,26 @@ const modelHttpErrorsQuery = (product: "go" | "zen") => { { column: "user_agent", op: "contains", value: "opencode" }, { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" }, ] + const failedHttpStatus = calculatedField({ + name: "is_failed_http_status", + expression: + product === "go" + ? `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401")), NOT(EQUALS($status, "429"))), 1, 0)` + : `IF(AND(EQUALS($status, "429"), $isFreeTier), 0, AND(GTE($status, "400"), NOT(EQUALS($status, "401"))), 1, 0)`, + }) return honeycomb.getQuerySpecificationOutput({ breakdowns: ["model"], - calculatedFields: [ - { - name: "is_failed_http_status", - expression: - product === "go" - ? `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401")), NOT(EQUALS($status, "429"))), 1, 0)` - : `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401"))), 1, 0)`, - }, - ], + calculatedFields: [failedHttpStatus], calculations: [ { op: "COUNT", name: "TOTAL", filterCombination: "AND", filters }, - { op: "SUM", name: "FAILED", column: "is_failed_http_status", filterCombination: "AND", filters }, + { + op: "SUM", + name: "FAILED", + column: failedHttpStatus.name, + filterCombination: "AND", + filters, + }, ], formulas: [{ name: "ERROR", expression: "IF(GTE($TOTAL, 100), DIV($FAILED, $TOTAL), 0)" }], timeRange: 900, @@ -59,31 +76,30 @@ const providerHttpErrorsQuery = (product: "go" | "zen") => { { column: "user_agent", op: "contains", value: "opencode" }, { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" }, ] + const successHttpStatus = calculatedField({ + name: "is_success_http_status", + expression: `IF(AND(GTE($status, "200"), LT($status, "400")), 1, 0)`, + }) + const failedProviderHttpStatus = calculatedField({ + name: "is_failed_provider_http_status", + expression: `IF(GT($llm.error.code, "400"), 1, 0)`, + }) return honeycomb.getQuerySpecificationOutput({ breakdowns: ["provider"], - calculatedFields: [ - { - name: "is_success_http_status", - expression: `IF(AND(GTE($status, "200"), LT($status, "400")), 1, 0)`, - }, - { - name: "is_failed_provider_http_status", - expression: `IF(GT($llm.error.code, "400"), 1, 0)`, - }, - ], + calculatedFields: [successHttpStatus, failedProviderHttpStatus], calculations: [ { op: "SUM", name: "SUCCESS", - column: "is_success_http_status", + column: successHttpStatus.name, filterCombination: "AND", filters: [...filters, { column: "event_type", op: "=", value: "completions" }], }, { op: "SUM", name: "FAILED", - column: "is_failed_provider_http_status", + column: failedProviderHttpStatus.name, filterCombination: "AND", filters: [...filters, { column: "event_type", op: "=", value: "llm.error" }], }, @@ -95,8 +111,6 @@ const providerHttpErrorsQuery = (product: "go" | "zen") => { }).json } -const description = "Managed by SST (Don't edit in Honeycomb UI)" - new honeycomb.Trigger("IncreasedModelHttpErrorsGo", { name: "Increased Model HTTP Errors [Go]", description, From d821650b4867398420b58e2a620e3b55e4c22190 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 11 May 2026 16:12:32 +0200 Subject: [PATCH 020/378] add default diff parser for markdown fenced code blocks (#26887) --- packages/opencode/parsers-config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/opencode/parsers-config.ts b/packages/opencode/parsers-config.ts index b4951afa2..9d6ba4b7b 100644 --- a/packages/opencode/parsers-config.ts +++ b/packages/opencode/parsers-config.ts @@ -286,5 +286,13 @@ export default { ], }, }, + { + filetype: "diff", + aliases: ["udiff", "patch"], + wasm: "https://github.com/tree-sitter-grammars/tree-sitter-diff/releases/download/v0.1.0/tree-sitter-diff.wasm", + queries: { + highlights: ["https://raw.githubusercontent.com/tree-sitter-grammars/tree-sitter-diff/master/queries/highlights.scm"], + }, + }, ], } From 6592d804603ea9e4166cffb33cd4309e91e892d3 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 14:13:39 +0000 Subject: [PATCH 021/378] chore: generate --- packages/opencode/parsers-config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/parsers-config.ts b/packages/opencode/parsers-config.ts index 9d6ba4b7b..9bcb9e85c 100644 --- a/packages/opencode/parsers-config.ts +++ b/packages/opencode/parsers-config.ts @@ -291,7 +291,9 @@ export default { aliases: ["udiff", "patch"], wasm: "https://github.com/tree-sitter-grammars/tree-sitter-diff/releases/download/v0.1.0/tree-sitter-diff.wasm", queries: { - highlights: ["https://raw.githubusercontent.com/tree-sitter-grammars/tree-sitter-diff/master/queries/highlights.scm"], + highlights: [ + "https://raw.githubusercontent.com/tree-sitter-grammars/tree-sitter-diff/master/queries/highlights.scm", + ], }, }, ], From 64c504212aa71eef9b0a3d009bcc4229b98057a1 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 10:22:45 -0400 Subject: [PATCH 022/378] Parse command config with Effect Schema (#26801) --- packages/opencode/src/config/command.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/config/command.ts b/packages/opencode/src/config/command.ts index c611f3c19..fa64aef8c 100644 --- a/packages/opencode/src/config/command.ts +++ b/packages/opencode/src/config/command.ts @@ -1,12 +1,10 @@ export * as ConfigCommand from "./command" import * as Log from "@opencode-ai/core/util/log" -import { Schema } from "effect" +import { Cause, Exit, Schema } from "effect" import { NamedError } from "@opencode-ai/core/util/error" import { Glob } from "@opencode-ai/core/util/glob" import { Bus } from "@/bus" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" import { configEntryNameFromPath } from "./entry-name" import { InvalidError } from "./error" import * as ConfigMarkdown from "./markdown" @@ -20,10 +18,12 @@ export const Info = Schema.Struct({ agent: Schema.optional(Schema.String), model: Schema.optional(ConfigModelID), subtask: Schema.optional(Schema.Boolean), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type Info = Schema.Schema.Type +const decodeInfo = Schema.decodeUnknownExit(Info) + export async function load(dir: string) { const result: Record = {} for (const item of await Glob.scan("{command,commands}/**/*.md", { @@ -51,12 +51,12 @@ export async function load(dir: string) { ...md.data, template: md.content.trim(), } - const parsed = Info.zod.safeParse(config) - if (parsed.success) { - result[config.name] = parsed.data + const parsed = decodeInfo(config, { errors: "all", propertyOrder: "original" }) + if (Exit.isSuccess(parsed)) { + result[config.name] = parsed.value continue } - throw new InvalidError({ path: item, issues: parsed.error.issues }, { cause: parsed.error }) + throw new InvalidError({ path: item, message: Cause.pretty(parsed.cause) }, { cause: Cause.squash(parsed.cause) }) } return result } From bcee24798852ed40166bf6905ab0b8a77be4f531 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 11:05:31 -0400 Subject: [PATCH 023/378] Define project update input with Effect Schema (#26803) --- packages/opencode/src/project/project.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 25feb657c..91d272ea6 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -1,4 +1,3 @@ -import z from "zod" import { and } from "drizzle-orm" import { Database } from "@/storage/db" import { eq } from "drizzle-orm" @@ -89,13 +88,13 @@ export function fromRow(row: Row): Info { } } -export const UpdateInput = z.object({ - projectID: ProjectID.zod, - name: z.string().optional(), - icon: zod(ProjectIcon).optional(), - commands: zod(ProjectCommands).optional(), +export const UpdateInput = Schema.Struct({ + projectID: ProjectID, + name: Schema.optional(Schema.String), + icon: Schema.optional(ProjectIcon), + commands: Schema.optional(ProjectCommands), }) -export type UpdateInput = z.infer +export type UpdateInput = Types.DeepMutable> export const UpdatePayload = Schema.Struct({ name: Schema.optional(Schema.String), From f240bba8e70bb1e0fd197dbb06a136f4f696c888 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 11:10:18 -0400 Subject: [PATCH 024/378] chore(http-recorder): remove content-matching dispatch mode (#26792) --- packages/http-recorder/README.md | 23 +++++------ packages/http-recorder/src/effect.ts | 10 ++--- packages/http-recorder/src/matching.ts | 18 --------- .../http-recorder/test/record-replay.test.ts | 39 ++++--------------- packages/llm/AGENTS.md | 2 +- 5 files changed, 20 insertions(+), 72 deletions(-) diff --git a/packages/http-recorder/README.md b/packages/http-recorder/README.md index f6aaed435..5920c9670 100644 --- a/packages/http-recorder/README.md +++ b/packages/http-recorder/README.md @@ -70,19 +70,15 @@ Cassettes are normal source files — review them, diff them, commit them. ## Request matching -By default, requests match on canonicalized method, URL, headers, and JSON -body (object keys sorted). Two dispatch strategies are available: +Replay walks the cassette in record order via an internal cursor: the Nth +request executed at runtime is served by the Nth recorded interaction, and +each one is validated as the cursor advances. Request equality is computed +on canonicalized method, URL, headers, and JSON body (object keys sorted). -- **`match`** (default) — find the first recorded interaction whose request - matches the incoming request. Same request twice returns the same response. -- **`sequential`** — return interactions in the order they were recorded, - validating each one matches as the cursor advances. Use for ordered flows - where the same URL is hit multiple times with meaningful state changes - (pagination, retries, polling). - -```ts -HttpRecorder.cassetteLayer("flow/poll-until-done", { dispatch: "sequential" }) -``` +This is deliberately strict — content-based dispatch was removed because +it silently returns the first recorded response for repeated identical +requests, masking state changes that retry/polling/cache-hit tests need to +observe. If you reorder requests in a test, re-record the cassette. Supply your own matcher via `match: (incoming, recorded) => boolean` for custom equivalence (e.g. ignoring a timestamp field in the body). @@ -194,7 +190,6 @@ type RecordReplayOptions = { directory?: string // default: /test/fixtures/recordings metadata?: Record // merged into cassette.metadata redactor?: Redactor // default: Redactor.defaults() - dispatch?: "match" | "sequential" // default: "match" match?: (incoming, recorded) => boolean // custom matcher } ``` @@ -211,4 +206,4 @@ type RecordReplayOptions = { | `redaction.ts` | Lower-level header/URL primitives + secret pattern detection. | | `schema.ts` | Effect Schema definitions for the cassette JSON format. | | `storage.ts` | Path resolution, JSON encode/decode, sync existence check. | -| `matching.ts` | Request matcher, canonicalization, dispatch strategies, mismatch diagnostics. | +| `matching.ts` | Request matcher, canonicalization, sequential cursor, mismatch diagnostics. | diff --git a/packages/http-recorder/src/effect.ts b/packages/http-recorder/src/effect.ts index e6c3ccbc1..61193a013 100644 --- a/packages/http-recorder/src/effect.ts +++ b/packages/http-recorder/src/effect.ts @@ -11,7 +11,7 @@ import { UrlParams, } from "effect/unstable/http" import * as CassetteService from "./cassette" -import { defaultMatcher, selectMatch, selectSequential, type RequestMatcher } from "./matching" +import { defaultMatcher, selectSequential, type RequestMatcher } from "./matching" import { appendOrFail, makeReplayState, resolveAutoMode } from "./recorder" import { defaults, type Redactor } from "./redactor" import { redactUrl } from "./redaction" @@ -24,7 +24,6 @@ export interface RecordReplayOptions { readonly directory?: string readonly metadata?: CassetteMetadata readonly redactor?: Redactor - readonly dispatch?: "match" | "sequential" readonly match?: RequestMatcher } @@ -71,7 +70,6 @@ export const recordingLayer = ( const match = options.match ?? defaultMatcher const requested = options.mode ?? "auto" const mode = requested === "auto" ? yield* resolveAutoMode(cassetteService, name) : requested - const sequential = options.dispatch === "sequential" const replay = yield* makeReplayState(cassetteService, name, httpInteractions) const snapshotRequest = (request: HttpClientRequest.HttpClientRequest) => @@ -119,14 +117,12 @@ export const recordingLayer = ( transportError(request, `Fixture "${name}" not found. Run locally to record it (CI=true forces replay).`), ), ) - const result = sequential - ? selectSequential(interactions, incoming, match, yield* replay.cursor) - : selectMatch(interactions, incoming, match) + const result = selectSequential(interactions, incoming, match, yield* replay.cursor) if (!result.interaction) return yield* Effect.fail( transportError(request, `Fixture "${name}" does not match the current request: ${result.detail}.`), ) - if (sequential) yield* replay.advance + yield* replay.advance return HttpClientResponse.fromWeb( request, new Response(decodeResponseBody(result.interaction.response), result.interaction.response), diff --git a/packages/http-recorder/src/matching.ts b/packages/http-recorder/src/matching.ts index 9af85a2f3..ab647ab37 100644 --- a/packages/http-recorder/src/matching.ts +++ b/packages/http-recorder/src/matching.ts @@ -92,24 +92,6 @@ export const requestDiff = (expected: RequestSnapshot, received: RequestSnapshot return lines } -export const mismatchDetail = (interactions: ReadonlyArray, incoming: RequestSnapshot): string => { - if (interactions.length === 0) return "cassette has no recorded HTTP interactions" - const ranked = interactions - .map((interaction, index) => ({ index, lines: requestDiff(interaction.request, incoming) })) - .toSorted((a, b) => a.lines.length - b.lines.length || a.index - b.index) - const best = ranked[0] - return ["no recorded interaction matched", `closest interaction: #${best.index + 1}`, ...best.lines].join("\n") -} - -export const selectMatch = ( - interactions: ReadonlyArray, - incoming: RequestSnapshot, - match: RequestMatcher, -): { readonly interaction: HttpInteraction | undefined; readonly detail: string } => { - const interaction = interactions.find((candidate) => match(incoming, candidate.request)) - return { interaction, detail: interaction ? "" : mismatchDetail(interactions, incoming) } -} - export const selectSequential = ( interactions: ReadonlyArray, incoming: RequestSnapshot, diff --git a/packages/http-recorder/test/record-replay.test.ts b/packages/http-recorder/test/record-replay.test.ts index 7613563fd..503f87ac5 100644 --- a/packages/http-recorder/test/record-replay.test.ts +++ b/packages/http-recorder/test/record-replay.test.ts @@ -230,41 +230,19 @@ describe("http-recorder", () => { ) }) - test("default matcher dispatches multi-interaction cassettes by request shape", async () => { - await run( - Effect.gen(function* () { - expect(yield* post("https://example.test/echo", { step: 2 })).toBe('{"reply":"second"}') - expect(yield* post("https://example.test/echo", { step: 1 })).toBe('{"reply":"first"}') - }), - ) - }) - - test("sequential dispatch returns recorded responses in order for identical requests", async () => { - await runWith( - "record-replay/retry", - { dispatch: "sequential" }, - Effect.gen(function* () { - expect(yield* post("https://example.test/poll", { id: "job_1" })).toBe('{"status":"pending"}') - expect(yield* post("https://example.test/poll", { id: "job_1" })).toBe('{"status":"complete"}') - }), - ) - }) - - test("default matcher returns the first match for identical requests", async () => { + test("replay returns recorded responses in order for identical requests", async () => { await runWith( "record-replay/retry", {}, Effect.gen(function* () { expect(yield* post("https://example.test/poll", { id: "job_1" })).toBe('{"status":"pending"}') - expect(yield* post("https://example.test/poll", { id: "job_1" })).toBe('{"status":"pending"}') + expect(yield* post("https://example.test/poll", { id: "job_1" })).toBe('{"status":"complete"}') }), ) }) - test("sequential dispatch reports cursor exhaustion when more requests are made than recorded", async () => { - await runWith( - "record-replay/multi-step", - { dispatch: "sequential" }, + test("replay reports cursor exhaustion when more requests are made than recorded", async () => { + await run( Effect.gen(function* () { yield* post("https://example.test/echo", { step: 1 }) yield* post("https://example.test/echo", { step: 2 }) @@ -274,10 +252,8 @@ describe("http-recorder", () => { ) }) - test("sequential dispatch still validates each recorded request", async () => { - await runWith( - "record-replay/multi-step", - { dispatch: "sequential" }, + test("replay validates each recorded request in order", async () => { + await run( Effect.gen(function* () { yield* post("https://example.test/echo", { step: 1 }) const exit = yield* Effect.exit(post("https://example.test/echo", { step: 3 })) @@ -331,14 +307,13 @@ describe("http-recorder", () => { } }) - test("mismatch diagnostics show closest redacted request differences", async () => { + test("mismatch diagnostics show redacted request differences against the expected interaction", async () => { await run( Effect.gen(function* () { const exit = yield* Effect.exit( post("https://example.test/echo?api_key=secret-value", { step: 3, token: "sk-123456789012345678901234" }), ) const message = failureText(exit) - expect(message).toContain("closest interaction: #1") expect(message).toContain("url:") expect(message).toContain("https://example.test/echo?api_key=%5BREDACTED%5D") expect(message).toContain("body:") diff --git a/packages/llm/AGENTS.md b/packages/llm/AGENTS.md index b20847da3..8d51b5cfe 100644 --- a/packages/llm/AGENTS.md +++ b/packages/llm/AGENTS.md @@ -289,6 +289,6 @@ Filters apply in replay and record mode. Combine them with `RECORD=true` when re **Binary response bodies.** Most providers stream text (SSE, JSON). AWS Bedrock streams binary AWS event-stream frames whose CRC32 fields would be mangled by a UTF-8 round-trip — those bodies are stored as base64 with `bodyEncoding: "base64"` on the response snapshot. Detection is by `Content-Type` in `@opencode-ai/http-recorder` (currently `application/vnd.amazon.eventstream` and `application/octet-stream`); cassettes for SSE/JSON routes omit the field and decode as text. -**Matching strategies.** Replay defaults to structural matching, which finds an interaction by comparing method, URL, allow-listed headers, and the canonical JSON body. This is the right choice for tool loops because each round's request differs (the message history grows). For scenarios where successive requests are byte-identical and expect different responses (retries, polling), pass `dispatch: "sequential"` in `RecordReplayOptions` — replay then walks the cassette in record order via an internal cursor. `scriptedResponses` (in `test/lib/http.ts`) is the deterministic counterpart for tests that don't need a live provider; it scripts response bodies in order without reading from disk. +**Matching strategy.** Replay walks the cassette in record order via an internal cursor: the Nth runtime request is served by the Nth recorded interaction, and each one is validated by comparing method, URL, allow-listed headers, and the canonical JSON body. This handles tool loops (each round's request differs as history grows) and retry/polling scenarios (successive byte-identical requests with different responses) uniformly. If a test reorders its requests, re-record the cassette. `scriptedResponses` (in `test/lib/http.ts`) is the deterministic counterpart for tests that don't need a live provider; it scripts response bodies in order without reading from disk. Do not blanket re-record an entire test file when adding one cassette. `RECORD=true` rewrites every recorded case that runs, and provider streams contain volatile IDs, timestamps, fingerprints, and obfuscation fields. Prefer deleting the one cassette you intend to refresh, or run a focused test pattern that only registers the scenario you want to record. Keep stable existing cassettes unchanged unless their request shape or expected behavior changed. From 4bae84c8b0bf8bf491bdea07722a3e53a56d8cc6 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Mon, 11 May 2026 20:50:03 +0530 Subject: [PATCH 025/378] feat(scout): autocomplete configured mentions (#26843) --- .../cmd/tui/component/prompt/autocomplete.tsx | 150 +++++++++++++++++- packages/opencode/src/config/config.ts | 2 +- packages/opencode/src/session/prompt.ts | 116 +++++++++----- packages/opencode/test/session/prompt.test.ts | 66 +++++++- packages/web/src/content/docs/tui.mdx | 6 + 5 files changed, 293 insertions(+), 47 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 7f390f0eb..3242de94d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -18,6 +18,8 @@ import { Locale } from "@/util/locale" import type { PromptInfo } from "./history" import { useFrecency } from "./frecency" import { useBindings } from "../../keymap" +import { Reference } from "@/reference/reference" +import type { Config } from "@/config/config" function removeLineRange(input: string) { const hashIndex = input.lastIndexOf("#") @@ -260,6 +262,87 @@ export function Autocomplete(props: { } } + function createReferenceFilePart(input: { + alias: string + root: string + item: string + lineRange?: { startLine: number; endLine?: number } + }) { + const filename = `${input.alias}/${ + input.lineRange && !input.item.endsWith("/") + ? `${input.item}#${input.lineRange.startLine}${input.lineRange.endLine ? `-${input.lineRange.endLine}` : ""}` + : input.item + }` + const urlObj = pathToFileURL(path.join(input.root, input.item)) + + if (input.lineRange && !input.item.endsWith("/")) { + urlObj.searchParams.set("start", String(input.lineRange.startLine)) + if (input.lineRange.endLine !== undefined) { + urlObj.searchParams.set("end", String(input.lineRange.endLine)) + } + } + + return { + filename, + url: urlObj.href, + part: { + type: "file" as const, + mime: input.item.endsWith("/") ? "application/x-directory" : "text/plain", + filename, + url: urlObj.href, + source: { + type: "file" as const, + text: { + start: 0, + end: 0, + value: "", + }, + path: filename, + }, + }, + } + } + + function referencePromptText(reference: Reference.Resolved) { + const problem = reference.kind === "invalid" ? reference.message : undefined + return [ + `Referenced configured reference @${reference.name}.`, + ...(reference.kind === "local" ? ["Kind: local directory"] : []), + ...(reference.kind === "git" ? ["Kind: git repository"] : []), + ...(reference.kind === "invalid" ? [`Repository: ${reference.repository}`] : []), + ...(reference.kind === "git" ? [`Repository: ${reference.repository}`] : []), + ...(reference.kind === "git" && reference.branch ? [`Branch/ref: ${reference.branch}`] : []), + ...(reference.kind === "invalid" ? [] : [`Reference root: ${reference.path}`]), + ...(problem + ? [`Problem: ${problem}`] + : [ + "For targeted context, inspect the reference path directly with Read, Glob, and Grep. For broader research, call the task tool with subagent scout and include this reference path.", + ]), + ].join("\n") + } + + const references = createMemo(() => + Reference.resolveAll({ + references: (sync.data.config.reference ?? {}) as NonNullable, + directory: sync.path.directory || process.cwd(), + worktree: sync.path.worktree || sync.path.directory || process.cwd(), + }), + ) + + const referenceSearch = createMemo(() => { + if (!store.visible || store.visible === "/") return + const { lineRange, baseQuery } = extractLineRange(search()) + const slash = baseQuery.indexOf("/") + if (slash === -1) return + const reference = references().find((item) => item.name === baseQuery.slice(0, slash)) + if (!reference || reference.kind === "invalid") return + return { + reference, + query: baseQuery.slice(slash + 1), + lineRange, + } + }) + function normalizeMentionPath(filePath: string) { const baseDir = sync.path.directory || process.cwd() const absolute = path.resolve(filePath) @@ -291,6 +374,7 @@ export function Autocomplete(props: { () => search(), async (query) => { if (!store.visible || store.visible === "/") return [] + if (referenceSearch()) return [] const { lineRange, baseQuery } = extractLineRange(query ?? "") @@ -339,6 +423,43 @@ export function Autocomplete(props: { }, ) + const [referenceFiles] = createResource( + () => referenceSearch(), + async (match) => { + if (!match) return [] + + const result = await sdk.client.find.files({ + directory: match.reference.path, + query: match.query, + limit: 50, + }) + + if (result.error || !result.data) return [] + + const width = props.anchor().width - 4 + return result.data.map((item): AutocompleteOption => { + const { filename, part } = createReferenceFilePart({ + alias: match.reference.name, + root: match.reference.path, + item, + lineRange: match.lineRange, + }) + return { + display: Locale.truncateMiddle(filename, width), + value: filename, + isDirectory: item.endsWith("/"), + path: filename, + onSelect: () => { + insertPart(filename, part) + }, + } + }) + }, + { + initialValue: [], + }, + ) + const mcpResources = createMemo(() => { if (!store.visible || store.visible === "/") return [] @@ -397,6 +518,22 @@ export function Autocomplete(props: { ) }) + const referenceAliases = createMemo(() => + references().map( + (reference): AutocompleteOption => ({ + display: "@" + reference.name, + description: reference.kind === "invalid" ? reference.message : " configured reference", + onSelect: () => { + insertPart(reference.name, { + type: "text", + text: referencePromptText(reference), + synthetic: true, + }) + }, + }), + ), + ) + const commands = createMemo((): AutocompleteOption[] => { const results: AutocompleteOption[] = [...command.slashes()] @@ -428,11 +565,18 @@ export function Autocomplete(props: { const options = createMemo((prev: AutocompleteOption[] | undefined) => { const filesValue = files() + const referenceFilesValue = referenceFiles() + const referenceSearchValue = referenceSearch() const agentsValue = agents() + const referenceAliasesValue = referenceAliases() const commandsValue = commands() const mixed: AutocompleteOption[] = - store.visible === "@" ? [...agentsValue, ...(filesValue || []), ...mcpResources()] : [...commandsValue] + store.visible === "@" + ? referenceSearchValue + ? referenceFilesValue || [] + : [...referenceAliasesValue, ...agentsValue, ...(filesValue || []), ...mcpResources()] + : [...commandsValue] const searchValue = search() @@ -440,7 +584,7 @@ export function Autocomplete(props: { return mixed } - if (files.loading && prev && prev.length > 0) { + if ((files.loading || referenceFiles.loading) && prev && prev.length > 0) { return prev } @@ -505,7 +649,7 @@ export function Autocomplete(props: { const input = props.input() const currentCursorOffset = input.cursorOffset - const displayText = selected.display.trimEnd() + const displayText = (selected.value ?? selected.display).trimEnd() const path = displayText.startsWith("@") ? displayText.slice(1) : displayText input.cursorOffset = store.index diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 114a38803..c05d562c9 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -145,7 +145,7 @@ export const Info = Schema.Struct({ }), skills: Schema.optional(ConfigSkills.Info).annotate({ description: "Additional skill folder paths" }), reference: Schema.optional(ConfigReference.Info).annotate({ - description: "Named git or local directory references that can be @ mentioned as Scout-backed subagents", + description: "Named git or local directory references that can be mentioned as @alias or @alias/path", }), watcher: Schema.optional( Schema.Struct({ diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 3b919e2f0..6033b0944 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -121,6 +121,45 @@ function referencePromptMetadata(input: unknown): ReferencePromptMetadata | unde } } +function referenceTextPart(input: { + reference: Reference.Resolved + source: ReferencePromptMetadata["source"] + target?: string + targetPath?: string + problem?: string +}): MessageV2.TextPartInput { + const metadata: ReferencePromptMetadata = { + name: input.reference.name, + kind: input.reference.kind, + ...(input.reference.kind === "invalid" ? { repository: input.reference.repository } : { path: input.reference.path }), + ...(input.reference.kind === "git" ? { repository: input.reference.repository, branch: input.reference.branch } : {}), + ...(input.target === undefined ? {} : { target: input.target }), + ...(input.targetPath ? { targetPath: input.targetPath } : {}), + problem: input.problem ?? (input.reference.kind === "invalid" ? input.reference.message : undefined), + source: input.source, + } + const label = metadata.target === undefined ? `@${metadata.name}` : `@${metadata.name}/${metadata.target}` + return { + type: "text", + synthetic: true, + text: [ + `Referenced configured reference ${label}.`, + ...(metadata.kind === "local" ? ["Kind: local directory"] : []), + ...(metadata.kind === "git" ? ["Kind: git repository"] : []), + ...(metadata.repository ? [`Repository: ${metadata.repository}`] : []), + ...(metadata.branch ? [`Branch/ref: ${metadata.branch}`] : []), + ...(metadata.path ? [`Reference root: ${metadata.path}`] : []), + ...(metadata.targetPath ? [`Resolved path: ${metadata.targetPath}`] : []), + ...(metadata.problem + ? [`Problem: ${metadata.problem}`] + : [ + "For targeted context, inspect the reference path directly with Read, Glob, and Grep. For broader research, call the task tool with subagent scout and include this reference path.", + ]), + ].join("\n"), + metadata: { reference: metadata }, + } +} + export interface Interface { readonly cancel: (sessionID: SessionID) => Effect.Effect readonly prompt: (input: PromptInput) => Effect.Effect @@ -186,48 +225,6 @@ export const layer = Layer.effect( const start = match.index ?? 0 return { value: match[0], start, end: start + match[0].length } } - const referenceTextPart = (input: { - reference: Reference.Resolved - source: ReturnType - target?: string - targetPath?: string - problem?: string - }): MessageV2.TextPartInput => { - const metadata: ReferencePromptMetadata = { - name: input.reference.name, - kind: input.reference.kind, - ...(input.reference.kind === "invalid" - ? { repository: input.reference.repository } - : { path: input.reference.path }), - ...(input.reference.kind === "git" - ? { repository: input.reference.repository, branch: input.reference.branch } - : {}), - ...(input.target === undefined ? {} : { target: input.target }), - ...(input.targetPath ? { targetPath: input.targetPath } : {}), - problem: input.problem ?? (input.reference.kind === "invalid" ? input.reference.message : undefined), - source: input.source, - } - const label = metadata.target === undefined ? `@${metadata.name}` : `@${metadata.name}/${metadata.target}` - return { - type: "text", - synthetic: true, - text: [ - `Referenced configured reference ${label}.`, - ...(metadata.kind === "local" ? ["Kind: local directory"] : []), - ...(metadata.kind === "git" ? ["Kind: git repository"] : []), - ...(metadata.repository ? [`Repository: ${metadata.repository}`] : []), - ...(metadata.branch ? [`Branch/ref: ${metadata.branch}`] : []), - ...(metadata.path ? [`Reference root: ${metadata.path}`] : []), - ...(metadata.targetPath ? [`Resolved path: ${metadata.targetPath}`] : []), - ...(metadata.problem - ? [`Problem: ${metadata.problem}`] - : [ - "For targeted context, inspect the reference path directly with Read, Glob, and Grep. For broader research, call the task tool with subagent scout and include this reference path.", - ]), - ].join("\n"), - metadata: { reference: metadata }, - } - } yield* Effect.forEach( files, Effect.fnUntraced(function* (match) { @@ -1156,6 +1153,30 @@ NOTE: At any point in time through this workflow you should feel free to ask the id: part.id ? PartID.make(part.id) : PartID.ascending(), }) + const referenceContextFromFilePart = Effect.fnUntraced(function* ( + part: Extract, + filepath: string, + ) { + const name = part.filename?.replace(/#\d+(?:-\d*)?$/, "") + if (!name) return + const slash = name.indexOf("/") + if (slash === -1) return + + const reference = yield* references.get(name.slice(0, slash)) + if (!reference || reference.kind === "invalid") return + if (!AppFileSystem.contains(reference.path, filepath)) return + + const target = path.relative(reference.path, filepath).split(path.sep).join("/") + if (!target || target.startsWith("../") || target === "..") return + + return referenceTextPart({ + reference, + source: part.source?.text ?? { value: `@${name}`, start: 0, end: name.length + 1 }, + target, + targetPath: filepath, + }) + }) + const resolvePart: (part: PromptInput["parts"][number]) => Effect.Effect[]> = Effect.fn( "SessionPrompt.resolveUserPart", )(function* (part) { @@ -1238,6 +1259,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the case "file:": { log.info("file", { mime: part.mime }) const filepath = fileURLToPath(part.url) + const referenceContext = yield* referenceContextFromFilePart(part, filepath) const mime = (yield* fsys.isDir(filepath)) ? "application/x-directory" : part.mime const { read } = yield* registry.named() @@ -1283,6 +1305,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the } const args = { filePath: filepath, offset, limit } const pieces: Draft[] = [ + ...(referenceContext + ? [{ ...referenceContext, messageID: info.id, sessionID: input.sessionID }] + : []), { messageID: info.id, sessionID: input.sessionID, @@ -1348,6 +1373,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the error: new NamedError.Unknown({ message }).toObject(), }) return [ + ...(referenceContext + ? [{ ...referenceContext, messageID: info.id, sessionID: input.sessionID }] + : []), { messageID: info.id, sessionID: input.sessionID, @@ -1358,6 +1386,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the ] } return [ + ...(referenceContext + ? [{ ...referenceContext, messageID: info.id, sessionID: input.sessionID }] + : []), { messageID: info.id, sessionID: input.sessionID, @@ -1377,6 +1408,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the } return [ + ...(referenceContext ? [{ ...referenceContext, messageID: info.id, sessionID: input.sessionID }] : []), { messageID: info.id, sessionID: input.sessionID, diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index f5c167465..104346530 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -4,7 +4,7 @@ import { expect } from "bun:test" import { Cause, Effect, Exit, Fiber, Layer } from "effect" import fs from "fs/promises" import path from "path" -import { fileURLToPath } from "url" +import { fileURLToPath, pathToFileURL } from "url" import { NamedError } from "@opencode-ai/core/util/error" import { Agent as AgentSvc } from "../../src/agent/agent" import { Bus } from "../../src/bus" @@ -1889,6 +1889,70 @@ it.live("injects metadata for bare configured reference mentions", () => ), ) +it.live("injects metadata for configured reference file attachments", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const docs = path.join(dir, "external-docs") + const readme = path.join(docs, "README.md") + yield* Effect.promise(() => fs.mkdir(docs, { recursive: true })) + yield* Effect.promise(() => Bun.write(readme, "reference readme")) + + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + const message = yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [ + { type: "text", text: "Read @docs/README.md" }, + { + type: "file", + mime: "text/plain", + filename: "docs/README.md", + url: pathToFileURL(readme).href, + source: { + type: "file", + path: "docs/README.md", + text: { value: "@docs/README.md", start: 5, end: 20 }, + }, + }, + ], + }) + + const stored = MessageV2.get({ sessionID: session.id, messageID: message.info.id }) + const synthetic = stored.parts.filter( + (part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true, + ) + const reference = synthetic.find((part) => part.text.startsWith("Referenced configured reference @docs/README.md.")) + + expect(reference?.metadata?.reference).toMatchObject({ + name: "docs", + kind: "local", + path: docs, + target: "README.md", + targetPath: readme, + source: { value: "@docs/README.md", start: 5, end: 20 }, + }) + expect(synthetic.findIndex((part) => part === reference)).toBeLessThan( + synthetic.findIndex((part) => part.text.startsWith("Called the Read tool with the following input:")), + ) + + yield* sessions.remove(session.id) + }), + { + git: true, + config: { + ...cfg, + reference: { + docs: "./external-docs", + }, + }, + }, + ), +) + // Special characters in filenames it.live("handles filenames with # character", () => diff --git a/packages/web/src/content/docs/tui.mdx b/packages/web/src/content/docs/tui.mdx index 72d9658d1..b9103c702 100644 --- a/packages/web/src/content/docs/tui.mdx +++ b/packages/web/src/content/docs/tui.mdx @@ -41,6 +41,12 @@ How is auth handled in @packages/functions/src/api/index.ts? The content of the file is added to the conversation automatically. +Configured references also appear in `@` autocomplete. Type `@alias` to add the reference root as context, or type `@alias/` to autocomplete files inside that reference. + +```text "@docs/README.md" +Compare our setup with @docs/README.md +``` + --- ## Bash commands From 19fce2bc6f0a42e750553e4627f5ca6b6c26dfac Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 15:21:20 +0000 Subject: [PATCH 026/378] chore: generate --- packages/opencode/src/session/prompt.ts | 8 ++++++-- packages/opencode/test/session/prompt.test.ts | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 6033b0944..4950be084 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -131,8 +131,12 @@ function referenceTextPart(input: { const metadata: ReferencePromptMetadata = { name: input.reference.name, kind: input.reference.kind, - ...(input.reference.kind === "invalid" ? { repository: input.reference.repository } : { path: input.reference.path }), - ...(input.reference.kind === "git" ? { repository: input.reference.repository, branch: input.reference.branch } : {}), + ...(input.reference.kind === "invalid" + ? { repository: input.reference.repository } + : { path: input.reference.path }), + ...(input.reference.kind === "git" + ? { repository: input.reference.repository, branch: input.reference.branch } + : {}), ...(input.target === undefined ? {} : { target: input.target }), ...(input.targetPath ? { targetPath: input.targetPath } : {}), problem: input.problem ?? (input.reference.kind === "invalid" ? input.reference.message : undefined), diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 104346530..382195494 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1925,7 +1925,9 @@ it.live("injects metadata for configured reference file attachments", () => const synthetic = stored.parts.filter( (part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true, ) - const reference = synthetic.find((part) => part.text.startsWith("Referenced configured reference @docs/README.md.")) + const reference = synthetic.find((part) => + part.text.startsWith("Referenced configured reference @docs/README.md."), + ) expect(reference?.metadata?.reference).toMatchObject({ name: "docs", From 52f7ba7d4d6bdd924586bfddf41f19bcc4a07528 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 11:48:30 -0400 Subject: [PATCH 027/378] fix(llm): drop removed dispatch option from recorded cache tests (#26900) --- .../test/provider/anthropic-messages-cache.recorded.test.ts | 5 ++--- .../test/provider/bedrock-converse-cache.recorded.test.ts | 5 ++--- packages/llm/test/provider/gemini-cache.recorded.test.ts | 5 ++--- .../test/provider/openai-responses-cache.recorded.test.ts | 6 +++--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts b/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts index cb144b1a5..68b7e0a4a 100644 --- a/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts +++ b/packages/llm/test/provider/anthropic-messages-cache.recorded.test.ts @@ -31,10 +31,9 @@ const recorded = recordedTests({ provider: "anthropic", protocol: "anthropic-messages", requires: ["ANTHROPIC_API_KEY"], - // Two identical requests in one cassette — match by recording order so the - // second call replays the cached-hit interaction. + // Two identical requests in one cassette — replay walks the cassette in + // recording order so the second call replays the cached-hit interaction. options: { - dispatch: "sequential", redactor: Redactor.defaults({ requestHeaders: { allow: ["content-type", "anthropic-version"] } }), }, }) diff --git a/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts b/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts index 16c44099c..2771046f8 100644 --- a/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts +++ b/packages/llm/test/provider/bedrock-converse-cache.recorded.test.ts @@ -38,9 +38,8 @@ const recorded = recordedTests({ provider: "amazon-bedrock", protocol: "bedrock-converse", requires: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"], - // Two identical requests in one cassette — match by recording order so the - // second call replays the cached-hit interaction. - options: { dispatch: "sequential" }, + // Two identical requests in one cassette — replay walks the cassette in + // recording order so the second call replays the cached-hit interaction. }) describe("Bedrock Converse cache recorded", () => { diff --git a/packages/llm/test/provider/gemini-cache.recorded.test.ts b/packages/llm/test/provider/gemini-cache.recorded.test.ts index c3b3e55b3..b86980c43 100644 --- a/packages/llm/test/provider/gemini-cache.recorded.test.ts +++ b/packages/llm/test/provider/gemini-cache.recorded.test.ts @@ -29,9 +29,8 @@ const recorded = recordedTests({ provider: "google", protocol: "gemini", requires: ["GOOGLE_GENERATIVE_AI_API_KEY"], - // Two identical requests in one cassette — match by recording order so the - // second call replays the cached-hit interaction. - options: { dispatch: "sequential" }, + // Two identical requests in one cassette — replay walks the cassette in + // recording order so the second call replays the cached-hit interaction. }) describe("Gemini cache recorded", () => { diff --git a/packages/llm/test/provider/openai-responses-cache.recorded.test.ts b/packages/llm/test/provider/openai-responses-cache.recorded.test.ts index 5a38898c0..2b67a0a4f 100644 --- a/packages/llm/test/provider/openai-responses-cache.recorded.test.ts +++ b/packages/llm/test/provider/openai-responses-cache.recorded.test.ts @@ -29,9 +29,9 @@ const recorded = recordedTests({ provider: "openai", protocol: "openai-responses", requires: ["OPENAI_API_KEY"], - // Two identical requests in one cassette — match by recording order so the - // second call replays the cached-hit interaction, not the cold-miss one. - options: { dispatch: "sequential" }, + // Two identical requests in one cassette — replay walks the cassette in + // recording order so the second call replays the cached-hit interaction, + // not the cold-miss one. }) describe("OpenAI Responses cache recorded", () => { From 023e1c711eeb8b286dd2d1d3c842cce2cd895b1b Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 13:05:41 -0400 Subject: [PATCH 028/378] refactor(llm): colocate per-type factories on their namespaces (#26799) --- packages/llm/AGENTS.md | 10 ++++-- packages/llm/src/llm.ts | 32 +++---------------- packages/llm/test/cache-policy.test.ts | 10 +++--- packages/llm/test/llm.test.ts | 28 ++++++++-------- .../anthropic-messages.recorded.test.ts | 10 +++--- .../test/provider/anthropic-messages.test.ts | 28 ++++++++-------- .../test/provider/bedrock-converse.test.ts | 32 +++++++++---------- packages/llm/test/provider/gemini.test.ts | 10 +++--- .../llm/test/provider/openai-chat.test.ts | 12 +++---- .../provider/openai-compatible-chat.test.ts | 8 ++--- .../test/provider/openai-responses.test.ts | 10 +++--- packages/llm/test/recorded-scenarios.ts | 6 ++-- packages/llm/test/tool-runtime.test.ts | 6 ++-- 13 files changed, 91 insertions(+), 111 deletions(-) diff --git a/packages/llm/AGENTS.md b/packages/llm/AGENTS.md index 8d51b5cfe..16a58fd86 100644 --- a/packages/llm/AGENTS.md +++ b/packages/llm/AGENTS.md @@ -8,6 +8,10 @@ - In `Effect.gen`, yield yieldable errors directly (`return yield* new MyError(...)`) instead of `Effect.fail(new MyError(...))`. - Use `Effect.void` instead of `Effect.succeed(undefined)` when the successful value is intentionally void. +## Conventions + +Per-type constructors live on the type's namespace, not as top-level re-exports. Use `Message.user(...)`, `Message.assistant(...)`, `Message.tool(...)`, `ToolDefinition.make(...)`, `ToolCallPart.make(...)`, `ToolResultPart.make(...)`, `ToolChoice.make(...)`, `ToolChoice.named(...)`, `SystemPart.make(...)`, and `GenerationOptions.make(...)` directly. The top-level `LLM` namespace is reserved for the request-shaped call API: `LLM.request`, `LLM.generate`, `LLM.stream`, `LLM.model`, `LLM.updateRequest`, `LLM.generateObject`. Two ways to construct the same thing is one too many. + ## Tests - Use `testEffect(...)` from `test/lib/effect.ts` for tests requiring Effect layers. @@ -166,12 +170,12 @@ If you find yourself copying a 3-to-5-line snippet between two protocols, lift i Tool loops are represented in common messages and events: ```ts -const call = LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } }) -const result = LLM.toolMessage({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }) +const call = ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } }) +const result = Message.tool({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }) const followUp = LLM.request({ model, - messages: [LLM.user("Weather?"), LLM.assistant([call]), result], + messages: [Message.user("Weather?"), Message.assistant([call]), result], }) ``` diff --git a/packages/llm/src/llm.ts b/packages/llm/src/llm.ts index bca78c888..6f6728216 100644 --- a/packages/llm/src/llm.ts +++ b/packages/llm/src/llm.ts @@ -44,32 +44,8 @@ export type RequestInput = Omit< export const limits = modelLimits -export const text = Message.text - -export const system = SystemPart.make - -export const message = Message.make - -export const user = Message.user - -export const assistant = Message.assistant - export const model = modelRef -export const toolDefinition = ToolDefinition.make - -export const toolCall = ToolCallPart.make - -export const toolResult = ToolResultPart.make - -export const toolMessage = Message.tool - -export const toolChoiceName = ToolChoice.named - -export const toolChoice = ToolChoice.make - -export const generation = GenerationOptions.make - export const generate = LLMClient.generate export const stream = LLMClient.stream @@ -95,10 +71,10 @@ export const request = (input: RequestInput) => { return new LLMRequest({ ...rest, system: SystemPart.content(requestSystem), - messages: [...(messages?.map(message) ?? []), ...(prompt === undefined ? [] : [user(prompt)])], - tools: tools?.map(toolDefinition) ?? [], - toolChoice: requestToolChoice ? toolChoice(requestToolChoice) : undefined, - generation: requestGeneration === undefined ? undefined : generation(requestGeneration), + messages: [...(messages?.map(Message.make) ?? []), ...(prompt === undefined ? [] : [Message.user(prompt)])], + tools: tools?.map(ToolDefinition.make) ?? [], + toolChoice: requestToolChoice ? ToolChoice.make(requestToolChoice) : undefined, + generation: requestGeneration === undefined ? undefined : GenerationOptions.make(requestGeneration), providerOptions: requestProviderOptions, http: requestHttp === undefined ? undefined : HttpOptions.make(requestHttp), }) diff --git a/packages/llm/test/cache-policy.test.ts b/packages/llm/test/cache-policy.test.ts index e742ca5e6..bb65a5636 100644 --- a/packages/llm/test/cache-policy.test.ts +++ b/packages/llm/test/cache-policy.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import { Effect } from "effect" -import { CacheHint, LLM } from "../src" +import { CacheHint, LLM, Message } from "../src" import { LLMClient } from "../src/route" import * as AnthropicMessages from "../src/protocols/anthropic-messages" import * as BedrockConverse from "../src/protocols/bedrock-converse" @@ -59,7 +59,7 @@ describe("applyCachePolicy", () => { model: anthropicModel, system: "Sys A", tools: [{ name: "t1", description: "t1", inputSchema: { type: "object", properties: {} } }], - messages: [LLM.user("first user"), LLM.assistant("assistant reply"), LLM.user("latest user message")], + messages: [Message.user("first user"), Message.assistant("assistant reply"), Message.user("latest user message")], cache: "auto", }), ) @@ -122,7 +122,7 @@ describe("applyCachePolicy", () => { model: bedrockModel, system: "Sys", tools: [{ name: "t1", description: "t1", inputSchema: { type: "object", properties: {} } }], - messages: [LLM.user("first user"), LLM.assistant("reply"), LLM.user("latest user")], + messages: [Message.user("first user"), Message.assistant("reply"), Message.user("latest user")], cache: "auto", }), ) @@ -221,7 +221,7 @@ describe("applyCachePolicy", () => { const prepared = yield* LLMClient.prepare( LLM.request({ model: anthropicModel, - messages: [LLM.user("u1"), LLM.assistant("a1"), LLM.user("u2"), LLM.assistant("a2")], + messages: [Message.user("u1"), Message.assistant("a1"), Message.user("u2"), Message.assistant("a2")], cache: { messages: { tail: 2 } }, }), ) @@ -239,7 +239,7 @@ describe("applyCachePolicy", () => { const prepared = yield* LLMClient.prepare( LLM.request({ model: anthropicModel, - messages: [LLM.user("u1"), LLM.assistant("a1"), LLM.user("u2")], + messages: [Message.user("u1"), Message.assistant("a1"), Message.user("u2")], cache: { messages: "latest-assistant" }, }), ) diff --git a/packages/llm/test/llm.test.ts b/packages/llm/test/llm.test.ts index e9ef58afa..c01fe33b2 100644 --- a/packages/llm/test/llm.test.ts +++ b/packages/llm/test/llm.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import { LLM, LLMResponse } from "../src" -import { LLMRequest, Message, ModelRef, ToolChoice, ToolDefinition } from "../src/schema" +import { LLMRequest, Message, ModelRef, ToolCallPart, ToolChoice, ToolDefinition, ToolResultPart } from "../src/schema" describe("llm constructors", () => { test("builds canonical schema classes from ergonomic input", () => { @@ -28,7 +28,7 @@ describe("llm constructors", () => { }) const updated = LLM.updateRequest(base, { generation: { maxTokens: 20 }, - messages: [...base.messages, LLM.assistant("Hi.")], + messages: [...base.messages, Message.assistant("Hi.")], }) expect(updated).toBeInstanceOf(LLMRequest) @@ -70,7 +70,7 @@ describe("llm constructors", () => { model: LLM.model({ id: "fake-model", provider: "fake", route: "openai-chat", baseURL: "https://fake.local" }), prompt: "Say hello.", }) - const updated = LLMRequest.update(base, { messages: [...base.messages, LLM.assistant("Hi.")] }) + const updated = LLMRequest.update(base, { messages: [...base.messages, Message.assistant("Hi.")] }) expect(updated).toBeInstanceOf(LLMRequest) expect(updated.id).toBe("req_1") @@ -91,18 +91,18 @@ describe("llm constructors", () => { }) test("builds tool choices from names and tools", () => { - const tool = LLM.toolDefinition({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }) + const tool = ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }) expect(tool).toBeInstanceOf(ToolDefinition) - expect(LLM.toolChoice("lookup")).toEqual(new ToolChoice({ type: "tool", name: "lookup" })) - expect(LLM.toolChoiceName("required")).toEqual(new ToolChoice({ type: "tool", name: "required" })) - expect(LLM.toolChoice(tool)).toEqual(new ToolChoice({ type: "tool", name: "lookup" })) + expect(ToolChoice.make("lookup")).toEqual(new ToolChoice({ type: "tool", name: "lookup" })) + expect(ToolChoice.named("required")).toEqual(new ToolChoice({ type: "tool", name: "required" })) + expect(ToolChoice.make(tool)).toEqual(new ToolChoice({ type: "tool", name: "lookup" })) }) test("builds tool choice modes from reserved strings", () => { - expect(LLM.toolChoice("auto")).toEqual(new ToolChoice({ type: "auto" })) - expect(LLM.toolChoice("none")).toEqual(new ToolChoice({ type: "none" })) - expect(LLM.toolChoice("required")).toEqual(new ToolChoice({ type: "required" })) + expect(ToolChoice.make("auto")).toEqual(new ToolChoice({ type: "auto" })) + expect(ToolChoice.make("none")).toEqual(new ToolChoice({ type: "none" })) + expect(ToolChoice.make("required")).toEqual(new ToolChoice({ type: "required" })) expect( LLM.request({ model: LLM.model({ id: "fake-model", provider: "fake", route: "openai-chat", baseURL: "https://fake.local" }), @@ -113,11 +113,11 @@ describe("llm constructors", () => { }) test("builds assistant tool calls and tool result messages", () => { - const call = LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } }) - const result = LLM.toolResult({ id: "call_1", name: "lookup", result: { temperature: 72 } }) + const call = ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } }) + const result = ToolResultPart.make({ id: "call_1", name: "lookup", result: { temperature: 72 } }) - expect(LLM.assistant([call]).content).toEqual([call]) - expect(LLM.toolMessage(result).content).toEqual([ + expect(Message.assistant([call]).content).toEqual([call]) + expect(Message.tool(result).content).toEqual([ { type: "tool-result", id: "call_1", name: "lookup", result: { type: "json", value: { temperature: 72 } } }, ]) }) diff --git a/packages/llm/test/provider/anthropic-messages.recorded.test.ts b/packages/llm/test/provider/anthropic-messages.recorded.test.ts index aa5b258d3..5fefae51d 100644 --- a/packages/llm/test/provider/anthropic-messages.recorded.test.ts +++ b/packages/llm/test/provider/anthropic-messages.recorded.test.ts @@ -1,7 +1,7 @@ import { Redactor } from "@opencode-ai/http-recorder" import { describe, expect } from "bun:test" import { Effect } from "effect" -import { LLM, LLMError } from "../../src" +import { LLM, LLMError, Message, ToolCallPart } from "../../src" import { LLMClient } from "../../src/route" import * as AnthropicMessages from "../../src/protocols/anthropic-messages" import { weatherToolName } from "../recorded-scenarios" @@ -16,12 +16,12 @@ const malformedToolOrderRequest = LLM.request({ id: "recorded_anthropic_malformed_tool_order", model, messages: [ - LLM.assistant([ - LLM.toolCall({ id: "call_1", name: weatherToolName, input: { city: "Paris" } }), + Message.assistant([ + ToolCallPart.make({ id: "call_1", name: weatherToolName, input: { city: "Paris" } }), { type: "text", text: "I will check the weather." }, ]), - LLM.toolMessage({ id: "call_1", name: weatherToolName, result: { temperature: "72F" } }), - LLM.user("Use that result to answer briefly."), + Message.tool({ id: "call_1", name: weatherToolName, result: { temperature: "72F" } }), + Message.user("Use that result to answer briefly."), ], tools: [{ name: weatherToolName, description: "Get weather", inputSchema: { type: "object", properties: {} } }], }) diff --git a/packages/llm/test/provider/anthropic-messages.test.ts b/packages/llm/test/provider/anthropic-messages.test.ts index a867d1659..0df3541d5 100644 --- a/packages/llm/test/provider/anthropic-messages.test.ts +++ b/packages/llm/test/provider/anthropic-messages.test.ts @@ -1,6 +1,6 @@ import { describe, expect } from "bun:test" import { Effect } from "effect" -import { CacheHint, LLM, LLMError, Usage } from "../../src" +import { CacheHint, LLM, LLMError, Message, ToolCallPart, Usage } from "../../src" import { LLMClient } from "../../src/route" import * as AnthropicMessages from "../../src/protocols/anthropic-messages" import { it } from "../lib/effect" @@ -47,9 +47,9 @@ describe("Anthropic Messages route", () => { id: "req_tool_result", model, messages: [ - LLM.user("What is the weather?"), - LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } })]), - LLM.toolMessage({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), + Message.user("What is the weather?"), + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } })]), + Message.tool({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), ], cache: "none", }), @@ -77,7 +77,7 @@ describe("Anthropic Messages route", () => { LLM.request({ model, messages: [ - LLM.assistant([ + Message.assistant([ { type: "reasoning", text: "thinking", providerMetadata: { anthropic: { signature: "sig_1" } } }, ]), ], @@ -304,8 +304,8 @@ describe("Anthropic Messages route", () => { id: "req_round_trip", model, messages: [ - LLM.user("Search for something."), - LLM.assistant([ + Message.user("Search for something."), + Message.assistant([ { type: "tool-call", id: "srvtoolu_abc", @@ -322,7 +322,7 @@ describe("Anthropic Messages route", () => { }, { type: "text", text: "Found it." }, ]), - LLM.user("Thanks."), + Message.user("Thanks."), ], }), ) @@ -355,7 +355,7 @@ describe("Anthropic Messages route", () => { id: "req_unknown_server_tool", model, messages: [ - LLM.assistant([ + Message.assistant([ { type: "tool-result", id: "srvtoolu_abc", @@ -378,7 +378,7 @@ describe("Anthropic Messages route", () => { LLM.request({ id: "req_media", model, - messages: [LLM.user({ type: "media", mediaType: "image/png", data: "AAECAw==" })], + messages: [Message.user({ type: "media", mediaType: "image/png", data: "AAECAw==" })], }), ).pipe(Effect.flip) @@ -416,9 +416,9 @@ describe("Anthropic Messages route", () => { }, ], messages: [ - LLM.user("What's the weather?"), - LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: {} })]), - LLM.toolMessage({ + Message.user("What's the weather?"), + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]), + Message.tool({ id: "call_1", name: "lookup", result: { temp: 72 }, @@ -501,7 +501,7 @@ describe("Anthropic Messages route", () => { }, ], system: [{ type: "text", text: "system-tail", cache: hint }], - messages: [LLM.user([{ type: "text", text: "message-tail", cache: hint }])], + messages: [Message.user([{ type: "text", text: "message-tail", cache: hint }])], }), ) diff --git a/packages/llm/test/provider/bedrock-converse.test.ts b/packages/llm/test/provider/bedrock-converse.test.ts index 208b56527..7d1ad3f30 100644 --- a/packages/llm/test/provider/bedrock-converse.test.ts +++ b/packages/llm/test/provider/bedrock-converse.test.ts @@ -2,7 +2,7 @@ import { EventStreamCodec } from "@smithy/eventstream-codec" import { fromUtf8, toUtf8 } from "@smithy/util-utf8" import { describe, expect } from "bun:test" import { Effect } from "effect" -import { CacheHint, LLM } from "../../src" +import { CacheHint, LLM, Message, ToolCallPart, ToolChoice } from "../../src" import { LLMClient } from "../../src/route" import * as BedrockConverse from "../../src/protocols/bedrock-converse" import { it } from "../lib/effect" @@ -94,7 +94,7 @@ describe("Bedrock Converse route", () => { inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] }, }, ], - toolChoice: LLM.toolChoice({ type: "required" }), + toolChoice: ToolChoice.make({ type: "required" }), }), ) @@ -124,9 +124,9 @@ describe("Bedrock Converse route", () => { id: "req_history", model, messages: [ - LLM.user("What is the weather?"), - LLM.assistant([LLM.toolCall({ id: "tool_1", name: "lookup", input: { query: "weather" } })]), - LLM.toolMessage({ id: "tool_1", name: "lookup", result: { forecast: "sunny" } }), + Message.user("What is the weather?"), + Message.assistant([ToolCallPart.make({ id: "tool_1", name: "lookup", input: { query: "weather" } })]), + Message.tool({ id: "tool_1", name: "lookup", result: { forecast: "sunny" } }), ], cache: "none", }), @@ -294,8 +294,8 @@ describe("Bedrock Converse route", () => { model, system: [{ type: "text", text: "System prefix.", cache }], messages: [ - LLM.user([{ type: "text", text: "User prefix.", cache }]), - LLM.assistant([{ type: "text", text: "Assistant prefix.", cache }]), + Message.user([{ type: "text", text: "User prefix.", cache }]), + Message.assistant([{ type: "text", text: "Assistant prefix.", cache }]), ], generation: { maxTokens: 16, temperature: 0 }, }), @@ -335,7 +335,7 @@ describe("Bedrock Converse route", () => { id: "req_image", model, messages: [ - LLM.user([ + Message.user([ { type: "text", text: "What is in this image?" }, { type: "media", mediaType: "image/png", data: "AAAA" }, { type: "media", mediaType: "image/jpeg", data: "BBBB" }, @@ -371,7 +371,7 @@ describe("Bedrock Converse route", () => { LLM.request({ id: "req_image_bytes", model, - messages: [LLM.user([{ type: "media", mediaType: "image/png", data: new Uint8Array([1, 2, 3, 4, 5]) }])], + messages: [Message.user([{ type: "media", mediaType: "image/png", data: new Uint8Array([1, 2, 3, 4, 5]) }])], }), ) @@ -394,7 +394,7 @@ describe("Bedrock Converse route", () => { id: "req_doc", model, messages: [ - LLM.user([ + Message.user([ { type: "media", mediaType: "application/pdf", data: "PDFDATA", filename: "report.pdf" }, { type: "media", mediaType: "text/csv", data: "CSVDATA" }, ]), @@ -424,7 +424,7 @@ describe("Bedrock Converse route", () => { LLM.request({ id: "req_bad_image", model, - messages: [LLM.user([{ type: "media", mediaType: "image/svg+xml", data: "x" }])], + messages: [Message.user([{ type: "media", mediaType: "image/svg+xml", data: "x" }])], }), ).pipe(Effect.flip) @@ -438,7 +438,7 @@ describe("Bedrock Converse route", () => { LLM.request({ id: "req_bad_doc", model, - messages: [LLM.user([{ type: "media", mediaType: "application/x-tar", data: "x", filename: "a.tar" }])], + messages: [Message.user([{ type: "media", mediaType: "application/x-tar", data: "x", filename: "a.tar" }])], }), ).pipe(Effect.flip) @@ -471,9 +471,9 @@ describe("Bedrock Converse route", () => { model, tools: [{ name: "lookup", description: "lookup", inputSchema: { type: "object", properties: {} }, cache }], messages: [ - LLM.user("What's the weather?"), - LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: {} })]), - LLM.toolMessage({ id: "call_1", name: "lookup", result: { temp: 72 }, cache }), + Message.user("What's the weather?"), + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]), + Message.tool({ id: "call_1", name: "lookup", result: { temp: 72 }, cache }), ], cache: "none", }), @@ -583,7 +583,7 @@ describe("Bedrock Converse recorded", () => { system: "Call tools exactly as requested.", prompt: "Call get_weather with city exactly Paris.", tools: [weatherTool], - toolChoice: LLM.toolChoice(weatherTool), + toolChoice: ToolChoice.make(weatherTool), cache: "none", generation: { maxTokens: 80, temperature: 0 }, }), diff --git a/packages/llm/test/provider/gemini.test.ts b/packages/llm/test/provider/gemini.test.ts index e0b3864a2..ea4eadc49 100644 --- a/packages/llm/test/provider/gemini.test.ts +++ b/packages/llm/test/provider/gemini.test.ts @@ -1,6 +1,6 @@ import { describe, expect } from "bun:test" import { Effect } from "effect" -import { LLM, LLMError, Usage } from "../../src" +import { LLM, LLMError, Message, ToolCallPart, Usage } from "../../src" import { LLMClient } from "../../src/route" import * as Gemini from "../../src/protocols/gemini" import { it } from "../lib/effect" @@ -49,12 +49,12 @@ describe("Gemini route", () => { ], toolChoice: { type: "tool", name: "lookup" }, messages: [ - LLM.user([ + Message.user([ { type: "text", text: "What is in this image?" }, { type: "media", mediaType: "image/png", data: "AAECAw==" }, ]), - LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } })]), - LLM.toolMessage({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } })]), + Message.tool({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), ], }), ) @@ -353,7 +353,7 @@ describe("Gemini route", () => { LLM.request({ id: "req_media", model, - messages: [LLM.assistant({ type: "media", mediaType: "image/png", data: "AAECAw==" })], + messages: [Message.assistant({ type: "media", mediaType: "image/png", data: "AAECAw==" })], }), ).pipe(Effect.flip) diff --git a/packages/llm/test/provider/openai-chat.test.ts b/packages/llm/test/provider/openai-chat.test.ts index 2c692dcd7..9c8142263 100644 --- a/packages/llm/test/provider/openai-chat.test.ts +++ b/packages/llm/test/provider/openai-chat.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { Effect, Schema, Stream } from "effect" import { HttpClientRequest } from "effect/unstable/http" -import { LLM, LLMError, Usage } from "../../src" +import { LLM, LLMError, Message, ToolCallPart, Usage } from "../../src" import * as Azure from "../../src/providers/azure" import * as OpenAI from "../../src/providers/openai" import * as OpenAIChat from "../../src/protocols/openai-chat" @@ -149,9 +149,9 @@ describe("OpenAI Chat route", () => { id: "req_tool_result", model, messages: [ - LLM.user("What is the weather?"), - LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } })]), - LLM.toolMessage({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), + Message.user("What is the weather?"), + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } })]), + Message.tool({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), ], }), ) @@ -185,7 +185,7 @@ describe("OpenAI Chat route", () => { LLM.request({ id: "req_media", model, - messages: [LLM.user({ type: "media", mediaType: "image/png", data: "AAECAw==" })], + messages: [Message.user({ type: "media", mediaType: "image/png", data: "AAECAw==" })], }), ).pipe(Effect.flip) @@ -199,7 +199,7 @@ describe("OpenAI Chat route", () => { LLM.request({ id: "req_reasoning", model, - messages: [LLM.assistant({ type: "reasoning", text: "hidden" })], + messages: [Message.assistant({ type: "reasoning", text: "hidden" })], }), ).pipe(Effect.flip) diff --git a/packages/llm/test/provider/openai-compatible-chat.test.ts b/packages/llm/test/provider/openai-compatible-chat.test.ts index 627e6ef4a..7759ff720 100644 --- a/packages/llm/test/provider/openai-compatible-chat.test.ts +++ b/packages/llm/test/provider/openai-compatible-chat.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { Effect, Schema } from "effect" import { HttpClientRequest } from "effect/unstable/http" -import { LLM } from "../../src" +import { LLM, Message, ToolCallPart } from "../../src" import { LLMClient } from "../../src/route" import * as OpenAICompatible from "../../src/providers/openai-compatible" import * as OpenAICompatibleChat from "../../src/protocols/openai-compatible-chat" @@ -157,9 +157,9 @@ describe("OpenAI-compatible Chat route", () => { ], toolChoice: "lookup", messages: [ - LLM.user("What is the weather?"), - LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } })]), - LLM.toolMessage({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), + Message.user("What is the weather?"), + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } })]), + Message.tool({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), ], }), ) diff --git a/packages/llm/test/provider/openai-responses.test.ts b/packages/llm/test/provider/openai-responses.test.ts index 2319857ed..da9dbd82c 100644 --- a/packages/llm/test/provider/openai-responses.test.ts +++ b/packages/llm/test/provider/openai-responses.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { ConfigProvider, Effect, Layer, Stream } from "effect" import { Headers, HttpClientRequest } from "effect/unstable/http" -import { LLM, LLMError, Usage } from "../../src" +import { LLM, LLMError, Message, ToolCallPart, Usage } from "../../src" import { Auth, LLMClient, RequestExecutor, WebSocketExecutor } from "../../src/route" import * as Azure from "../../src/providers/azure" import * as OpenAI from "../../src/providers/openai" @@ -251,9 +251,9 @@ describe("OpenAI Responses route", () => { id: "req_tool_result", model, messages: [ - LLM.user("What is the weather?"), - LLM.assistant([LLM.toolCall({ id: "call_1", name: "lookup", input: { query: "weather" } })]), - LLM.toolMessage({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), + Message.user("What is the weather?"), + Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } })]), + Message.tool({ id: "call_1", name: "lookup", result: { forecast: "sunny" } }), ], }), ) @@ -508,7 +508,7 @@ describe("OpenAI Responses route", () => { LLM.request({ id: "req_media", model, - messages: [LLM.user({ type: "media", mediaType: "image/png", data: "AAECAw==" })], + messages: [Message.user({ type: "media", mediaType: "image/png", data: "AAECAw==" })], }), ).pipe(Effect.flip) diff --git a/packages/llm/test/recorded-scenarios.ts b/packages/llm/test/recorded-scenarios.ts index 127a444a1..bdba8580f 100644 --- a/packages/llm/test/recorded-scenarios.ts +++ b/packages/llm/test/recorded-scenarios.ts @@ -1,6 +1,6 @@ import { expect } from "bun:test" import { Effect, Schema, Stream } from "effect" -import { LLM, LLMEvent, LLMResponse, type LLMRequest, type ModelRef } from "../src" +import { LLM, LLMEvent, LLMResponse, ToolChoice, ToolDefinition, type LLMRequest, type ModelRef } from "../src" import { LLMClient } from "../src/route" import { tool } from "../src/tool" @@ -18,7 +18,7 @@ export const LARGE_CACHEABLE_SYSTEM = (() => { return sentence.repeat(250) })() -export const weatherTool = LLM.toolDefinition({ +export const weatherTool = ToolDefinition.make({ name: weatherToolName, description: "Get current weather for a city.", inputSchema: { @@ -70,7 +70,7 @@ export const weatherToolRequest = (input: { system: "Call tools exactly as requested.", prompt: "Call get_weather with city exactly Paris.", tools: [weatherTool], - toolChoice: LLM.toolChoice(weatherTool), + toolChoice: ToolChoice.make(weatherTool), cache: "none", generation: input.temperature === false diff --git a/packages/llm/test/tool-runtime.test.ts b/packages/llm/test/tool-runtime.test.ts index 7251dee8a..8f4221784 100644 --- a/packages/llm/test/tool-runtime.test.ts +++ b/packages/llm/test/tool-runtime.test.ts @@ -1,6 +1,6 @@ import { describe, expect } from "bun:test" import { Effect, Schema, Stream } from "effect" -import { LLM, LLMEvent, LLMRequest, LLMResponse } from "../src" +import { GenerationOptions, LLM, LLMEvent, LLMRequest, LLMResponse, ToolChoice } from "../src" import { LLMClient } from "../src/route" import * as AnthropicMessages from "../src/protocols/anthropic-messages" import * as OpenAIChat from "../src/protocols/openai-chat" @@ -78,8 +78,8 @@ describe("LLMClient tools", () => { yield* TestToolRuntime.runTools({ request: LLMRequest.update(baseRequest, { - generation: LLM.generation({ maxTokens: 50 }), - toolChoice: LLM.toolChoice("auto"), + generation: GenerationOptions.make({ maxTokens: 50 }), + toolChoice: ToolChoice.make("auto"), }), tools: { get_weather }, }).pipe(Stream.runCollect, Effect.provide(layer)) From 25b12ed754adb6e23997010ac30582ed0cf936bf Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 17:06:57 +0000 Subject: [PATCH 029/378] chore: generate --- packages/llm/test/cache-policy.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/llm/test/cache-policy.test.ts b/packages/llm/test/cache-policy.test.ts index bb65a5636..ac700b58f 100644 --- a/packages/llm/test/cache-policy.test.ts +++ b/packages/llm/test/cache-policy.test.ts @@ -59,7 +59,11 @@ describe("applyCachePolicy", () => { model: anthropicModel, system: "Sys A", tools: [{ name: "t1", description: "t1", inputSchema: { type: "object", properties: {} } }], - messages: [Message.user("first user"), Message.assistant("assistant reply"), Message.user("latest user message")], + messages: [ + Message.user("first user"), + Message.assistant("assistant reply"), + Message.user("latest user message"), + ], cache: "auto", }), ) From df386bd6512aac7020ca788fc909cb7e7ccd61d2 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 13:13:41 -0400 Subject: [PATCH 030/378] feat(skill): enable customize-opencode by default, link full schema (#26899) --- packages/core/src/flag/flag.ts | 11 ----- packages/opencode/src/skill/index.ts | 12 +++--- .../src/skill/prompt/customize-opencode.md | 43 ++++++++++++++----- packages/opencode/test/preload.ts | 5 --- packages/opencode/test/skill/skill.test.ts | 20 ++++----- 5 files changed, 48 insertions(+), 43 deletions(-) diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index 3fe765575..175c723c5 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -1,5 +1,4 @@ import { Config } from "effect" -import { InstallationChannel } from "../installation/version" function truthy(key: string) { const value = process.env[key]?.toLowerCase() @@ -11,13 +10,6 @@ function falsy(key: string) { return value === "false" || value === "0" } -// Channels where new experiments default to ON (unstable / internal users). -// Stable channels (`prod`, `latest`) stay opt-in. -const UNSTABLE_CHANNELS = new Set(["dev", "beta", "local"]) -function unstableDefault(key: string) { - return truthy(key) || (!falsy(key) && UNSTABLE_CHANNELS.has(InstallationChannel)) -} - function number(key: string) { const value = process.env[key] if (!value) return undefined @@ -56,9 +48,6 @@ export const Flag = { OPENCODE_DISABLE_CLAUDE_CODE_PROMPT: OPENCODE_DISABLE_CLAUDE_CODE || truthy("OPENCODE_DISABLE_CLAUDE_CODE_PROMPT"), OPENCODE_DISABLE_CLAUDE_CODE_SKILLS, OPENCODE_DISABLE_EXTERNAL_SKILLS: truthy("OPENCODE_DISABLE_EXTERNAL_SKILLS"), - // Default-on for dev/beta/local; opt-in for stable. Set - // OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL=false to force off, =true to force on. - OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL: unstableDefault("OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL"), OPENCODE_FAKE_VCS: process.env["OPENCODE_FAKE_VCS"], OPENCODE_SERVER_PASSWORD: process.env["OPENCODE_SERVER_PASSWORD"], OPENCODE_SERVER_USERNAME: process.env["OPENCODE_SERVER_USERNAME"], diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 01bffdb02..e532efa3d 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -242,13 +242,11 @@ export const layer = Layer.effect( const s: State = { skills: {}, dirs: new Set() } // Register the built-in skill BEFORE disk discovery so a user-disk // skill with the same name can override it. - if (Flag.OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL) { - s.skills[CUSTOMIZE_OPENCODE_SKILL_NAME] = { - name: CUSTOMIZE_OPENCODE_SKILL_NAME, - description: CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION, - location: "", - content: CUSTOMIZE_OPENCODE_SKILL_BODY, - } + s.skills[CUSTOMIZE_OPENCODE_SKILL_NAME] = { + name: CUSTOMIZE_OPENCODE_SKILL_NAME, + description: CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION, + location: "", + content: CUSTOMIZE_OPENCODE_SKILL_BODY, } yield* loadSkills(s, yield* InstanceState.get(discovered), bus) return s diff --git a/packages/opencode/src/skill/prompt/customize-opencode.md b/packages/opencode/src/skill/prompt/customize-opencode.md index 6158aae08..744690b15 100644 --- a/packages/opencode/src/skill/prompt/customize-opencode.md +++ b/packages/opencode/src/skill/prompt/customize-opencode.md @@ -1,17 +1,39 @@ # Customizing opencode opencode validates its own config strictly and refuses to start when a field -is wrong. The shapes below are the accepted shapes. When in doubt, fetch -`https://opencode.ai/config.json` (the JSON Schema) and validate against it. +is wrong. The shapes below cover the common surface area, but they are a +**summary, not the source of truth**. -Every `opencode.json` should declare `"$schema": "https://opencode.ai/config.json"` -so the user's editor catches mistakes as they type. +## Full schema reference + +The authoritative list of every config option — with field types, enums, +defaults, and descriptions — lives in the published JSON Schema: + +**** + +If a field is not documented in this skill, or you need to confirm an exact +shape before writing config, **fetch that URL and read the schema directly** +rather than guessing. opencode hard-fails on invalid config, so the cost of a +wrong shape is a broken startup. + +Independently, every `opencode.json` should declare +`"$schema": "https://opencode.ai/config.json"` so the user's editor catches +mistakes as they type. + +## Applying changes + +Config is loaded once when opencode starts and is not hot-reloaded. After +saving changes to `opencode.json`, an agent file, a skill, a plugin, or any +other config-time file, **tell the user to quit and restart opencode** for +the changes to take effect. The running session will keep using the +already-loaded config until then. ## Where files live @@ -343,12 +365,13 @@ When a user's config is broken and opencode won't start, these env vars help: ## When proposing edits - Validate against the schema before writing. If you are unsure of a field's - exact shape, fetch `https://opencode.ai/config.json` rather than guessing. + exact shape, or the field is not covered in this skill, fetch + `https://opencode.ai/config.json` and read the schema rather than guessing. - Preserve `$schema` and any existing fields the user did not ask to change. - For agent, skill, and plugin definitions, prefer creating new files in the correct location over inlining everything in `opencode.json`. - If the user's existing config is malformed, point them at the env-var escape - hatch above so they can edit from inside opencode without breaking their + hatches above so they can edit from inside opencode without breaking their session. -- opencode hard-fails on invalid config by design. There is no graceful - degradation, so get the shape right the first time. +- After saving any config change, remind the user to quit and restart opencode + — running sessions keep using the already-loaded config. diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts index 1ba0554d3..b408f7ef1 100644 --- a/packages/opencode/test/preload.ts +++ b/packages/opencode/test/preload.ts @@ -35,11 +35,6 @@ process.env["XDG_CONFIG_HOME"] = path.join(dir, "config") process.env["XDG_STATE_HOME"] = path.join(dir, "state") process.env["OPENCODE_MODELS_PATH"] = path.join(import.meta.dir, "tool", "fixtures", "models-api.json") process.env["OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"] = "true" -// Tests assert exact skill counts from disk discovery; the built-in -// customize-opencode skill is opt-in for stable channels and on by default -// for unstable channels (including "local" where CI runs). Disable it here -// so disk-discovery tests aren't off-by-one. -process.env["OPENCODE_EXPERIMENTAL_CUSTOMIZE_SKILL"] = "false" // Set test home directory to isolate tests from user's actual home directory // This prevents tests from picking up real user configs/skills from ~/.claude/skills diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index d73750b08..969014e6b 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -63,7 +63,7 @@ Instructions here. ) const skill = yield* Skill.Service - const list = yield* skill.all() + const list = (yield* skill.all()).filter((s) => s.location !== "") expect(list.length).toBe(1) const item = list.find((x) => x.name === "test-skill") expect(item).toBeDefined() @@ -133,7 +133,7 @@ description: Second test skill. ) const skill = yield* Skill.Service - const list = yield* skill.all() + const list = (yield* skill.all()).filter((s) => s.location !== "") expect(list.length).toBe(2) expect(list.find((x) => x.name === "skill-one")).toBeDefined() expect(list.find((x) => x.name === "skill-two")).toBeDefined() @@ -157,7 +157,7 @@ Just some content without YAML frontmatter. ) const skill = yield* Skill.Service - expect(yield* skill.all()).toEqual([]) + expect((yield* skill.all()).filter((s) => s.location !== "")).toEqual([]) }), { git: true }, ), @@ -182,7 +182,7 @@ Instructions here. ) const skill = yield* Skill.Service - const list = yield* skill.all() + const list = (yield* skill.all()).filter((s) => s.location !== "") expect(list.length).toBe(1) const item = list.find((x) => x.name === "manual-skill") expect(item).toBeDefined() @@ -212,7 +212,7 @@ description: A skill in the .claude/skills directory. ) const skill = yield* Skill.Service - const list = yield* skill.all() + const list = (yield* skill.all()).filter((s) => s.location !== "") expect(list.length).toBe(1) const item = list.find((x) => x.name === "claude-skill") expect(item).toBeDefined() @@ -235,7 +235,7 @@ description: A skill in the .claude/skills directory. yield* Effect.promise(() => createGlobalSkill(tmp.path)) yield* Effect.gen(function* () { const skill = yield* Skill.Service - const list = yield* skill.all() + const list = (yield* skill.all()).filter((s) => s.location !== "") expect(list.length).toBe(1) expect(list[0].name).toBe("global-test-skill") expect(list[0].description).toBe("A global skill from ~/.claude/skills for testing.") @@ -251,7 +251,7 @@ description: A skill in the .claude/skills directory. () => Effect.gen(function* () { const skill = yield* Skill.Service - expect(yield* skill.all()).toEqual([]) + expect((yield* skill.all()).filter((s) => s.location !== "")).toEqual([]) }), { git: true }, ), @@ -275,7 +275,7 @@ description: A skill in the .agents/skills directory. ) const skill = yield* Skill.Service - const list = yield* skill.all() + const list = (yield* skill.all()).filter((s) => s.location !== "") expect(list.length).toBe(1) const item = list.find((x) => x.name === "agent-skill") expect(item).toBeDefined() @@ -314,7 +314,7 @@ This skill is loaded from the global home directory. yield* Effect.gen(function* () { const skill = yield* Skill.Service - const list = yield* skill.all() + const list = (yield* skill.all()).filter((s) => s.location !== "") expect(list.length).toBe(1) expect(list[0].name).toBe("global-agent-skill") expect(list[0].description).toBe("A global skill from ~/.agents/skills for testing.") @@ -355,7 +355,7 @@ description: A skill in the .agents/skills directory. ) const skill = yield* Skill.Service - const list = yield* skill.all() + const list = (yield* skill.all()).filter((s) => s.location !== "") expect(list.length).toBe(2) expect(list.find((x) => x.name === "claude-skill")).toBeDefined() expect(list.find((x) => x.name === "agent-skill")).toBeDefined() From 12583b18f0c7f169b7636b24844c17ba76388ded Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Mon, 11 May 2026 22:46:27 +0530 Subject: [PATCH 031/378] feat(tui): pin, quick-switch, and cycle recent sessions (#26858) --- packages/core/src/flag/flag.ts | 2 + packages/opencode/src/cli/cmd/tui/app.tsx | 51 ++++- .../cmd/tui/component/dialog-session-list.tsx | 131 ++++++++---- .../src/cli/cmd/tui/config/keybind.ts | 26 +++ .../src/cli/cmd/tui/context/local.tsx | 192 +++++++++++++++++- .../tui/feature-plugins/home/tips-view.tsx | 9 + 6 files changed, 373 insertions(+), 38 deletions(-) diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index 175c723c5..73ab3b055 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -85,6 +85,8 @@ export const Flag = { OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"], OPENCODE_EXPERIMENTAL_WORKSPACES: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES"), OPENCODE_EXPERIMENTAL_EVENT_SYSTEM: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"), + OPENCODE_EXPERIMENTAL_SESSION_SWITCHING: + OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SESSION_SWITCHING"), // Evaluated at access time (not module load) because tests, the CLI, and // external tooling set these env vars at runtime. diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index cc2afd1cd..d7f2cd14b 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -74,6 +74,17 @@ const appBindingCommands = [ "command.palette.show", "session.list", "session.new", + "session.cycle_recent", + "session.cycle_recent_reverse", + "session.quick_switch.1", + "session.quick_switch.2", + "session.quick_switch.3", + "session.quick_switch.4", + "session.quick_switch.5", + "session.quick_switch.6", + "session.quick_switch.7", + "session.quick_switch.8", + "session.quick_switch.9", "model.list", "model.cycle_recent", "model.cycle_recent_reverse", @@ -462,6 +473,37 @@ function App(props: { onSnapshot?: () => Promise }) { dialog.clear() }, }, + ...(Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING + ? [ + { + name: "session.cycle_recent", + title: "Cycle to previous recent session", + category: "Session", + hidden: true, + run: () => { + local.session.cycleRecent(1) + }, + }, + { + name: "session.cycle_recent_reverse", + title: "Cycle to next recent session", + category: "Session", + hidden: true, + run: () => { + local.session.cycleRecent(-1) + }, + }, + ...Array.from({ length: 9 }, (_, i) => ({ + name: `session.quick_switch.${i + 1}`, + title: `Switch to session in quick slot ${i + 1}`, + category: "Session", + hidden: true, + run: () => { + local.session.quickSwitch(i + 1) + }, + })), + ] + : []), { name: "model.list", title: "Switch model", @@ -776,7 +818,14 @@ function App(props: { onSnapshot?: () => Promise }) { useBindings(() => ({ enabled: command.matcher, - bindings: tuiConfig.keybinds.gather("app", appBindingCommands), + bindings: tuiConfig.keybinds.gather( + "app", + Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING + ? appBindingCommands + : appBindingCommands.filter( + (c) => !c.startsWith("session.cycle_recent") && !c.startsWith("session.quick_switch"), + ), + ), })) useBindings(() => ({ diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 35c966937..1dd33106d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -7,6 +7,7 @@ import { Locale } from "@/util/locale" import { useProject } from "@tui/context/project" import { useTheme } from "../context/theme" import { useSDK } from "../context/sdk" +import { useLocal } from "../context/local" import { Flag } from "@opencode-ai/core/flag/flag" import { DialogSessionRename } from "./dialog-session-rename" import { createDebouncedSignal } from "../util/signal" @@ -25,6 +26,7 @@ export function DialogSessionList() { const project = useProject() const { theme } = useTheme() const sdk = useSDK() + const local = useLocal() const toast = useToast() const [toDelete, setToDelete] = createSignal() const [search, setSearch] = createDebouncedSignal("", 150) @@ -128,7 +130,10 @@ export function DialogSessionList() { const [browseOrder] = createSignal(orderByRecency(sync.data.session)) + const RECENT_LIMIT = 5 + const options = createMemo(() => { + const enabled = Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING const today = new Date().toDateString() const sessionMap = new Map( sessions() @@ -139,46 +144,74 @@ export function DialogSessionList() { const searchResult = searchResults() const displayOrder = searchResult ? orderByRecency(searchResult) : browseOrder() - return displayOrder - .map((id) => sessionMap.get(id)) - .filter((x) => x !== undefined) - .map((x) => { - const workspace = x.workspaceID ? project.workspace.get(x.workspaceID) : undefined + const dismissed = enabled ? new Set(local.session.dismissedRecent()) : new Set() + const pinned = enabled ? local.session.pinned().filter((id) => sessionMap.has(id)) : [] + const pinnedSet = new Set(pinned) + const slotByID = enabled + ? new Map(local.session.slots().map((id, i) => [id, i + 1])) + : new Map() - let footer: JSX.Element | string = "" - if (Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) { - if (x.workspaceID) { - footer = workspace ? ( - - ) : ( - - ) - } - } else { - footer = Locale.time(x.time.updated) - } + const recent = enabled + ? displayOrder.filter((id) => !pinnedSet.has(id) && !dismissed.has(id)).slice(0, RECENT_LIMIT) + : [] + const recentSet = new Set(recent) - const date = new Date(x.time.updated) - let category = date.toDateString() - if (category === today) { - category = "Today" - } - const isDeleting = toDelete() === x.id - const status = sync.data.session_status?.[x.id] - const isWorking = status?.type === "busy" || status?.type === "retry" - return { - title: isDeleting ? `Press ${deleteHint()} again to confirm` : x.title, - bg: isDeleting ? theme.error : undefined, - value: x.id, - category, - footer, - gutter: isWorking ? () => : undefined, + function buildOption(id: string, category: string) { + const x = sessionMap.get(id) + if (!x) return undefined + const workspace = x.workspaceID ? project.workspace.get(x.workspaceID) : undefined + + let footer: JSX.Element | string = "" + if (Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) { + if (x.workspaceID) { + footer = workspace ? ( + + ) : ( + + ) } + } else { + footer = Locale.time(x.time.updated) + } + + const isDeleting = toDelete() === x.id + const status = sync.data.session_status?.[x.id] + const isWorking = status?.type === "busy" || status?.type === "retry" + const slot = slotByID.get(x.id) + const gutter = isWorking + ? () => + : slot !== undefined + ? () => {slot} + : undefined + return { + title: isDeleting ? `Press ${deleteHint()} again to confirm` : x.title, + bg: isDeleting ? theme.error : undefined, + value: x.id, + category, + footer, + gutter, + } + } + + const remaining = displayOrder + .filter((id) => !pinnedSet.has(id) && !recentSet.has(id)) + .map((id) => { + const x = sessionMap.get(id) + if (!x) return undefined + const label = new Date(x.time.updated).toDateString() + return buildOption(id, label === today ? "Today" : label) }) + .filter((x) => x !== undefined) + + return [ + ...pinned.map((id) => buildOption(id, "Pinned")).filter((x) => x !== undefined), + ...recent.map((id) => buildOption(id, "Recent")).filter((x) => x !== undefined), + ...remaining, + ] }) onMount(() => { @@ -203,6 +236,32 @@ export function DialogSessionList() { dialog.clear() }} actions={[ + ...(Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING + ? [ + { + command: "session.pin.toggle", + title: "pin/unpin", + onTrigger: (option: { value: string }) => { + local.session.togglePin(option.value) + }, + }, + { + command: "session.toggle.recent", + title: "toggle recent", + onTrigger: (option: { value: string }) => { + if (local.session.isPinned(option.value)) { + toast.show({ + variant: "info", + message: "Unpin the session first to toggle it in Recent", + duration: 3000, + }) + return + } + local.session.toggleRecent(option.value) + }, + }, + ] + : []), { command: "session.delete", title: "delete", diff --git a/packages/opencode/src/cli/cmd/tui/config/keybind.ts b/packages/opencode/src/cli/cmd/tui/config/keybind.ts index 46a48e18e..b20c87f30 100644 --- a/packages/opencode/src/cli/cmd/tui/config/keybind.ts +++ b/packages/opencode/src/cli/cmd/tui/config/keybind.ts @@ -80,6 +80,19 @@ const Definitions = { session_child_cycle: keybind("right", "Go to next child session"), session_child_cycle_reverse: keybind("left", "Go to previous child session"), session_parent: keybind("up", "Go to parent session"), + session_pin_toggle: keybind("ctrl+f", "Pin or unpin session in the session list"), + session_toggle_recent: keybind("ctrl+h", "Show or hide session in the Recent group"), + session_cycle_recent: keybind("]", "Cycle to the previous recent session"), + session_cycle_recent_reverse: keybind("[", "Cycle to the next recent session"), + session_quick_switch_1: keybind("1", "Switch to session in quick slot 1"), + session_quick_switch_2: keybind("2", "Switch to session in quick slot 2"), + session_quick_switch_3: keybind("3", "Switch to session in quick slot 3"), + session_quick_switch_4: keybind("4", "Switch to session in quick slot 4"), + session_quick_switch_5: keybind("5", "Switch to session in quick slot 5"), + session_quick_switch_6: keybind("6", "Switch to session in quick slot 6"), + session_quick_switch_7: keybind("7", "Switch to session in quick slot 7"), + session_quick_switch_8: keybind("8", "Switch to session in quick slot 8"), + session_quick_switch_9: keybind("9", "Switch to session in quick slot 9"), stash_delete: keybind("ctrl+d", "Delete stash entry"), model_provider_list: keybind("ctrl+a", "Open provider list from model dialog"), @@ -257,6 +270,19 @@ export const CommandMap = { session_child_cycle: "session.child.next", session_child_cycle_reverse: "session.child.previous", session_parent: "session.parent", + session_pin_toggle: "session.pin.toggle", + session_toggle_recent: "session.toggle.recent", + session_cycle_recent: "session.cycle_recent", + session_cycle_recent_reverse: "session.cycle_recent_reverse", + session_quick_switch_1: "session.quick_switch.1", + session_quick_switch_2: "session.quick_switch.2", + session_quick_switch_3: "session.quick_switch.3", + session_quick_switch_4: "session.quick_switch.4", + session_quick_switch_5: "session.quick_switch.5", + session_quick_switch_6: "session.quick_switch.6", + session_quick_switch_7: "session.quick_switch.7", + session_quick_switch_8: "session.quick_switch.8", + session_quick_switch_9: "session.quick_switch.9", stash_delete: "stash.delete", model_provider_list: "model.dialog.provider", model_favorite_toggle: "model.dialog.favorite", diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index 2958b573d..fc2226315 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -1,11 +1,14 @@ import { createStore } from "solid-js/store" import { createSimpleContext } from "./helper" -import { batch, createEffect, createMemo } from "solid-js" +import { batch, createEffect, createMemo, on } from "solid-js" import { useSync } from "@tui/context/sync" import { useTheme } from "@tui/context/theme" +import { useRoute } from "@tui/context/route" +import { useEvent } from "@tui/context/event" import { uniqueBy } from "remeda" import path from "path" import { Global } from "@opencode-ai/core/global" +import { Flag } from "@opencode-ai/core/flag/flag" import { iife } from "@/util/iife" import { useToast } from "../ui/toast" import { useArgs } from "./args" @@ -380,6 +383,192 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } }) + const session = iife(() => { + const [sessionStore, setSessionStore] = createStore<{ + ready: boolean + pinned: string[] + dismissedRecent: string[] + recentOrder: string[] + }>({ + ready: false, + pinned: [], + dismissedRecent: [], + recentOrder: [], + }) + + const filePath = path.join(Global.Path.state, "session.json") + const state = { + pending: false, + } + + function save() { + if (!sessionStore.ready) { + state.pending = true + return + } + state.pending = false + void Filesystem.writeJson(filePath, { + pinned: sessionStore.pinned, + dismissedRecent: sessionStore.dismissedRecent, + recentOrder: sessionStore.recentOrder, + }) + } + + Filesystem.readJson(filePath) + .then((x: any) => { + if (Array.isArray(x.pinned)) setSessionStore("pinned", x.pinned) + if (Array.isArray(x.dismissedRecent)) setSessionStore("dismissedRecent", x.dismissedRecent) + if (Array.isArray(x.recentOrder)) setSessionStore("recentOrder", x.recentOrder) + }) + .catch(() => {}) + .finally(() => { + setSessionStore("ready", true) + if (state.pending) save() + }) + + const route = useRoute() + const event = useEvent() + let cycling = false + + const slots = createMemo(() => { + const rootSessions = sync.data.session.filter((x) => x.parentID === undefined) + const existing = new Set(rootSessions.map((x) => x.id)) + const dismissed = new Set(sessionStore.dismissedRecent) + const pins = sessionStore.pinned.filter((id) => existing.has(id)) + const pinnedSet = new Set(pins) + const recent = rootSessions + .filter((x) => !pinnedSet.has(x.id) && !dismissed.has(x.id)) + .toSorted((a, b) => b.time.updated - a.time.updated) + .map((x) => x.id) + return [...pins, ...recent].slice(0, 9) + }) + + function prune(sessionID: string) { + batch(() => { + if (sessionStore.pinned.includes(sessionID)) { + setSessionStore( + "pinned", + sessionStore.pinned.filter((x) => x !== sessionID), + ) + } + if (sessionStore.dismissedRecent.includes(sessionID)) { + setSessionStore( + "dismissedRecent", + sessionStore.dismissedRecent.filter((x) => x !== sessionID), + ) + } + if (sessionStore.recentOrder.includes(sessionID)) { + setSessionStore( + "recentOrder", + sessionStore.recentOrder.filter((x) => x !== sessionID), + ) + } + save() + }) + } + + event.on("session.deleted", (evt) => { + prune(evt.properties.info.id) + }) + + if (Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING) { + createEffect( + on( + () => (sessionStore.ready && route.data.type === "session" ? route.data.sessionID : undefined), + (sessionID) => { + if (!sessionID) return + if (cycling) { + cycling = false + return + } + const filtered = sessionStore.recentOrder.filter((x) => x !== sessionID) + const next = [sessionID, ...filtered].slice(0, 20) + setSessionStore("recentOrder", next) + save() + }, + ), + ) + } + + return { + get ready() { + return sessionStore.ready + }, + pinned() { + return sessionStore.pinned + }, + dismissedRecent() { + return sessionStore.dismissedRecent + }, + recentOrder() { + return sessionStore.recentOrder + }, + slots, + isPinned(sessionID: string) { + return sessionStore.pinned.includes(sessionID) + }, + isDismissed(sessionID: string) { + return sessionStore.dismissedRecent.includes(sessionID) + }, + togglePin(sessionID: string) { + batch(() => { + const exists = sessionStore.pinned.includes(sessionID) + const next = exists + ? sessionStore.pinned.filter((x) => x !== sessionID) + : [sessionID, ...sessionStore.pinned] + setSessionStore("pinned", next) + save() + }) + }, + toggleRecent(sessionID: string) { + batch(() => { + const exists = sessionStore.dismissedRecent.includes(sessionID) + const next = exists + ? sessionStore.dismissedRecent.filter((x) => x !== sessionID) + : [sessionID, ...sessionStore.dismissedRecent] + setSessionStore("dismissedRecent", next) + save() + }) + }, + quickSwitch(slot: number) { + const target = slots()[slot - 1] + if (!target) return + if (route.data.type === "session" && route.data.sessionID === target) return + route.navigate({ type: "session", sessionID: target }) + }, + cycleRecent(direction: 1 | -1) { + if (route.data.type !== "session") { + toast.show({ + variant: "info", + message: "Open a session first to cycle between recent sessions", + duration: 3000, + }) + return + } + const current = route.data.sessionID + const order = sessionStore.recentOrder.filter((id) => + sync.data.session.some((s) => s.id === id && s.parentID === undefined), + ) + if (order.length < 2) { + toast.show({ + variant: "info", + message: "No other recent sessions to cycle to", + duration: 3000, + }) + return + } + const index = order.indexOf(current) + if (index === -1) return + const next = index + direction + if (next < 0 || next >= order.length) return + const target = order[next] + if (!target || target === current) return + cycling = true + route.navigate({ type: "session", sessionID: target }) + }, + } + }) + const mcp = { isEnabled(name: string) { const status = sync.data.mcp[name] @@ -412,6 +601,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ model, agent, mcp, + session, } return result }, 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 c7a7b211f..07a2844e9 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 @@ -1,5 +1,6 @@ import { createMemo, For } from "solid-js" import { DEFAULT_THEMES, useTheme } from "@tui/context/theme" +import { Flag } from "@opencode-ai/core/flag/flag" const themeCount = Object.keys(DEFAULT_THEMES).length const themeTip = `Use {highlight}/themes{/highlight} or {highlight}Ctrl+X T{/highlight} to switch between ${themeCount} built-in themes` @@ -66,6 +67,14 @@ const TIPS = [ themeTip, "Press {highlight}Ctrl+X N{/highlight} or {highlight}/new{/highlight} to start a fresh conversation session", "Use {highlight}/sessions{/highlight} or {highlight}Ctrl+X L{/highlight} to list and continue previous conversations", + ...(Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING + ? [ + "Press {highlight}Ctrl+F{/highlight} in the session list to pin a session so it stays at the top", + "Pinned and recent sessions are bound to {highlight}Ctrl+X 1{/highlight} through {highlight}Ctrl+X 9{/highlight} for one-press switching", + "Press {highlight}Ctrl+X ]{/highlight} / {highlight}Ctrl+X [{/highlight} to cycle through recently visited sessions", + "Press {highlight}Ctrl+H{/highlight} in the session list to show or hide a session in the Recent group", + ] + : []), "Run {highlight}/compact{/highlight} to summarize long sessions near context limits", "Press {highlight}Ctrl+X X{/highlight} or {highlight}/export{/highlight} to save the conversation as Markdown", "Press {highlight}Ctrl+X Y{/highlight} to copy the assistant's last message to clipboard", From cddab63808c394422aa519a01b07af52506e85d7 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 17:17:38 +0000 Subject: [PATCH 032/378] chore: generate --- packages/core/src/flag/flag.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index 73ab3b055..f76d1aaf9 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -85,8 +85,7 @@ export const Flag = { OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"], OPENCODE_EXPERIMENTAL_WORKSPACES: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES"), OPENCODE_EXPERIMENTAL_EVENT_SYSTEM: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"), - OPENCODE_EXPERIMENTAL_SESSION_SWITCHING: - OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SESSION_SWITCHING"), + OPENCODE_EXPERIMENTAL_SESSION_SWITCHING: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SESSION_SWITCHING"), // Evaluated at access time (not module load) because tests, the CLI, and // external tooling set these env vars at runtime. From 6f2f759fbb7ecce6c28bac6ab0d72891370827ac Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 13:20:54 -0400 Subject: [PATCH 033/378] Clean up post-Hono references (#26903) --- bun.lock | 6 - packages/console/function/package.json | 2 - packages/opencode/specs/effect/errors.md | 15 +- packages/opencode/specs/effect/routes.md | 27 +- packages/opencode/specs/effect/schema.md | 44 +- .../opencode/specs/effect/server-package.md | 694 ++---------------- .../instance/httpapi/handlers/provider.ts | 2 +- .../routes/instance/httpapi/handlers/pty.ts | 2 +- .../instance/httpapi/handlers/session.ts | 2 +- .../httpapi/middleware/compression.ts | 2 +- .../server/routes/instance/httpapi/public.ts | 8 +- .../test/project/instance-bootstrap.test.ts | 4 +- .../test/server/httpapi-config.test.ts | 2 +- .../test/server/httpapi-experimental.test.ts | 9 +- .../test/server/httpapi-instance.test.ts | 5 +- specs/v2/todo.md | 6 +- 16 files changed, 92 insertions(+), 738 deletions(-) diff --git a/bun.lock b/bun.lock index c3758e232..37df44b0b 100644 --- a/bun.lock +++ b/bun.lock @@ -152,12 +152,10 @@ "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", "@ai-sdk/openai-compatible": "2.0.37", - "@hono/zod-validator": "catalog:", "@openauthjs/openauth": "0.0.0-20250322224806", "@opencode-ai/console-core": "workspace:*", "@opencode-ai/console-resource": "workspace:*", "ai": "catalog:", - "hono": "catalog:", "zod": "catalog:", }, "devDependencies": { @@ -1235,8 +1233,6 @@ "@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="], - "@hono/zod-validator": ["@hono/zod-validator@0.4.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g=="], - "@ibm/plex": ["@ibm/plex@6.4.1", "", { "dependencies": { "@ibm/telemetry-js": "^1.5.1" } }, "sha512-fnsipQywHt3zWvsnlyYKMikcVI7E2fEwpiPnIHFqlbByXVfQfANAAeJk1IV4mNnxhppUIDlhU0TzwYwL++Rn2g=="], "@ibm/telemetry-js": ["@ibm/telemetry-js@1.11.0", "", { "bin": { "ibmtelemetry": "dist/collect.js" } }, "sha512-RO/9j+URJnSfseWg9ZkEX9p+a3Ousd33DBU7rOafoZB08RqdzxFVYJ2/iM50dkBuD0o7WX7GYt1sLbNgCoE+pA=="], @@ -5469,8 +5465,6 @@ "@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "@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=="], "@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 41487f845..5c1d1ba22 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -20,12 +20,10 @@ "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", "@ai-sdk/openai-compatible": "2.0.37", - "@hono/zod-validator": "catalog:", "@opencode-ai/console-core": "workspace:*", "@opencode-ai/console-resource": "workspace:*", "@openauthjs/openauth": "0.0.0-20250322224806", "ai": "catalog:", - "hono": "catalog:", "zod": "catalog:" } } diff --git a/packages/opencode/specs/effect/errors.md b/packages/opencode/specs/effect/errors.md index 746e65869..e19199ef4 100644 --- a/packages/opencode/specs/effect/errors.md +++ b/packages/opencode/specs/effect/errors.md @@ -23,8 +23,9 @@ contracts. - Some services already use `Schema.TaggedErrorClass`, for example `Account`, `Auth`, `Permission`, `Question`, `Installation`, and parts of `Workspace`. -- Legacy Hono error handling recognizes `NamedError`, `Session.BusyError`, and a - few name-based cases, then emits the legacy `{ name, data }` JSON body. +- The temporary HttpApi compatibility middleware recognizes `NamedError`, + `Session.BusyError`, and a few name-based cases, then emits the legacy + `{ name, data }` JSON body. - Effect `HttpApi` only knows how to encode errors that are declared on the endpoint, group, or middleware. Undeclared expected errors become defects and eventually fall through to generic HTTP handling. @@ -127,7 +128,7 @@ Create an HttpApi-local error module, likely That module should provide: - Legacy-compatible public schemas for `{ name, data }` error bodies that must - remain SDK-compatible during the Hono migration. + remain SDK-compatible while route groups declare typed errors. - Small constructors or mapping helpers for common API errors such as not found, bad request, conflict, and unknown internal errors. - Route-group-specific adapters only when they encode domain-specific public @@ -173,7 +174,7 @@ Add the `httpapi/errors.ts` module before converting route groups. - Define a legacy `{ name, data }` body helper for SDK-compatible errors. - Define `UnknownError` for generic internal failures with a safe public message. - Define `BadRequestError` and `NotFoundError` equivalents only if the actual - wire body must match the legacy Hono SDK surface. + wire body must match the existing SDK surface. - Put the HTTP status on the public schema with `HttpApiSchema.status(...)` or `{ httpApiStatus: code }`; do not keep a separate name-to-status table. - Keep conversion helpers pure and small. They should not inspect `Cause` or @@ -238,7 +239,7 @@ Suggested route order: 2. `experimental` worktree mutations. 3. `provider` auth and model selection errors. 4. `mcp` OAuth and connection errors. -5. Remaining route groups as Hono deletion work progresses. +5. Remaining route groups as typed error contracts are declared. ### 6. Remove Defect Recovery @@ -286,8 +287,8 @@ For HttpApi conversions: errors. - Add a regression test that the temporary middleware is no longer needed for the migrated route. -- Keep bridge/parity tests aligned with legacy Hono behavior until Hono is - deleted or the SDK contract intentionally changes. +- Keep compatibility tests aligned with the existing SDK contract until the + public error shape intentionally changes. ## Verification Commands diff --git a/packages/opencode/specs/effect/routes.md b/packages/opencode/specs/effect/routes.md index 3bf7e1b55..8066bda34 100644 --- a/packages/opencode/specs/effect/routes.md +++ b/packages/opencode/specs/effect/routes.md @@ -39,26 +39,19 @@ This eliminates multiple `runPromise` round-trips and lets handlers compose natu ## Current route files -Current instance route files live under `src/server/routes/instance`. - -Files that are already mostly on the intended service-yielding shape: - -- [x] `server/routes/instance/question.ts` — handlers yield `Question.Service` -- [x] `server/routes/instance/provider.ts` — handlers yield `Provider.Service`, `ProviderAuth.Service`, and `Config.Service` -- [x] `server/routes/instance/permission.ts` — handlers yield `Permission.Service` -- [x] `server/routes/instance/mcp.ts` — handlers mostly yield `MCP.Service` -- [x] `server/routes/instance/pty.ts` — handlers yield `Pty.Service` +Current instance route files live under `src/server/routes/instance/httpapi`. +Most handlers already yield stable services at route-layer construction and then +close over those services in endpoint implementations. Files still worth tracking here: -- [ ] `server/routes/instance/session.ts` — still the heaviest mixed file; many handlers are composed, but the file still mixes patterns and has direct `Bus.publish(...)` / `Session.list(...)` usage -- [ ] `server/routes/instance/index.ts` — mostly converted, but still has direct `Instance.dispose()` / `Instance.*` reads for `/instance/dispose` and `/path` -- [ ] `server/routes/instance/file.ts` — most handlers yield services, but `/find` still passes `Instance.directory` directly into ripgrep and `/find/symbol` is still stubbed -- [ ] `server/routes/instance/experimental.ts` — mixed state; many handlers are composed, but some still rely on `runRequest(...)` or direct `Instance.project` reads -- [ ] `server/routes/instance/middleware.ts` — still enters the instance via `Instance.provide(...)` -- [ ] `server/routes/global.ts` — still uses `Instance.disposeAll()` and remains partly outside the fully-composed style +- [ ] `handlers/session.ts` — still the heaviest mixed file; some paths keep compatibility translations and direct event publication +- [ ] `handlers/experimental.ts` — mixed state; some handlers still rely on request-local context reads +- [ ] `middleware/*` — still contains compatibility policy for auth, compression, errors, instance context, and workspace routing +- [ ] `public.ts` — still owns SDK/OpenAPI compatibility translation shims +- [ ] raw route modules — WebSocket and catch-all routes should stay explicit and avoid rebuilding stable layers per request ## Notes -- Route conversion is now less about facade removal and more about removing the remaining direct `Instance.*` reads, `Instance.provide(...)` boundaries, and small Promise-style bridges inside route files. -- `jsonRequest(...)` / `runRequest(...)` already provide a good intermediate shape for many handlers. The remaining cleanup is mostly consistency work in the heavier files. +- Route conversion is now less about backend migration and more about removing the remaining direct `Instance.*` reads, request-local service plumbing, and OpenAPI compatibility shims. +- Prefer route-layer service capture over rebuilding or providing stable layers inside individual handlers. diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index e20605c3b..20b3e70e7 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -305,38 +305,18 @@ emitted JSON Schema must stay byte-identical. ### HTTP route boundaries -Every file in `src/server/routes/` uses hono-openapi with zod validators for -route inputs/outputs. Migrating these individually is the last step; most -will switch to `.zod` derived from the Schema-migrated domain types above, -which means touching them is largely mechanical once the domain side is -done. - -- [ ] `src/server/error.ts` -- [x] `src/server/event.ts` -- [x] `src/server/projectors.ts` -- [ ] `src/server/routes/control/index.ts` -- [ ] `src/server/routes/control/workspace.ts` -- [ ] `src/server/routes/global.ts` -- [ ] `src/server/routes/instance/index.ts` -- [ ] `src/server/routes/instance/config.ts` -- [ ] `src/server/routes/instance/event.ts` -- [ ] `src/server/routes/instance/experimental.ts` -- [ ] `src/server/routes/instance/file.ts` -- [ ] `src/server/routes/instance/mcp.ts` -- [ ] `src/server/routes/instance/permission.ts` -- [ ] `src/server/routes/instance/project.ts` -- [ ] `src/server/routes/instance/provider.ts` -- [ ] `src/server/routes/instance/pty.ts` -- [ ] `src/server/routes/instance/question.ts` -- [ ] `src/server/routes/instance/session.ts` -- [ ] `src/server/routes/instance/sync.ts` -- [ ] `src/server/routes/instance/tui.ts` - -The bigger prize for this group is the `@effect/platform` HTTP migration -described in `specs/effect/http-api.md`. Once that lands, every one of -these files changes shape entirely (`HttpApi.endpoint(...)` and friends), -so the Schema-first domain types become a prerequisite rather than a -sibling task. +The server route tree now lives under `src/server/routes/instance/httpapi` and +uses Effect HttpApi contracts for request and response schemas. Remaining schema +work is no longer a Hono route migration; it is compatibility cleanup around +derived `.zod` statics, OpenAPI translation shims, and route groups that still +need explicit SDK-visible error contracts. + +Good follow-up targets: + +- shrink `public.ts` legacy OpenAPI translation shims one SDK-compatible slice at a time +- replace production `.zod.safeParse(...)` call sites with Effect Schema decoders +- remove derived `.zod` statics after their production consumers are gone +- declare route-group errors directly instead of relying on compatibility middleware ### Everything else diff --git a/packages/opencode/specs/effect/server-package.md b/packages/opencode/specs/effect/server-package.md index 06e89c18d..036472337 100644 --- a/packages/opencode/specs/effect/server-package.md +++ b/packages/opencode/specs/effect/server-package.md @@ -1,668 +1,58 @@ -# Server package extraction +# Server Package Extraction -Practical reference for extracting a future `packages/server` from the current `packages/opencode` monolith while `packages/core` is still being migrated to Effect. +Practical reference for a future `packages/server` split after the opencode +server moved to the Effect HttpApi backend. -This document is intentionally execution-oriented. +## Current State -It should give an agent enough context to land one incremental PR at a time without needing to rediscover the package strategy, route migration rules, or current constraints. +- The server still lives in `packages/opencode`. +- The runtime and app layer are centralized in `src/effect/app-runtime.ts` and + `src/effect/run-service.ts`. +- The route tree lives under `src/server/routes/instance/httpapi` and is hosted + from `src/server/server.ts`. +- OpenAPI generation is based on the HttpApi contract plus compatibility + translation in `src/server/routes/instance/httpapi/public.ts`. +- There is no standalone `packages/server` workspace yet. -## Goal - -Create `packages/server` as the home for: - -- HTTP contract definitions -- HTTP handler implementations -- OpenAPI generation -- eventual embeddable server APIs for Node apps - -Do this without blocking on the full `packages/core` extraction. - -## Future state +## Future State Target package layout: -- `packages/core` - all opencode services, Effect-first source of truth -- `packages/server` - opencode server, with separate contract and implementation, still producing `openapi.json` -- `packages/cli` - TUI + CLI entrypoints -- `packages/sdk` - generated from the server OpenAPI spec, may add higher-level wrappers -- `packages/plugin` - generated or semi-hand-rolled non-Effect package built from core plugin definitions - -Desired user stories: - -- import from `core` and build a custom agent or app-specific runtime -- import from `server` and embed the full opencode server into an existing Node app -- spawn the CLI and talk to the server through that boundary - -## Current state - -Everything still lives in `packages/opencode`. - -Important current facts: - -- there is no `packages/core` or `packages/cli` workspace yet -- there is no `packages/server` workspace yet on this branch -- the main host server is still Hono-based in `src/server/server.ts` -- current OpenAPI generation is Hono-based through `Server.openapi()` and `cli/cmd/generate.ts` -- the Effect runtime and app layer are centralized in `src/effect/app-runtime.ts` and `src/effect/run-service.ts` -- there are already bridged Effect `HttpApi` slices under `src/server/routes/instance/httpapi/*` -- those slices are mounted into the Hono server behind `OPENCODE_EXPERIMENTAL_HTTPAPI` -- the bridge currently covers `question`, `permission`, `provider`, partial `config`, and partial `project` routes - -This means the package split should start from an extraction path, not from greenfield package ownership. - -## Structural reference - -Use `anomalyco/opentunnel` as the structural reference for `packages/server`. - -The important pattern there is: - -- `packages/core` owns services and domain schemas -- `packages/server/src/definition/*` owns pure `HttpApi` contracts -- `packages/server/src/api/*` owns `HttpApiBuilder.group(...)` implementations and server-side middleware wiring -- `packages/server/src/index.ts` becomes the composition root only after the server package really owns runtime hosting - -Relevant `opentunnel` files: - -- `packages/server/src/definition/index.ts` -- `packages/server/src/definition/tunnel.ts` -- `packages/server/src/api/index.ts` -- `packages/server/src/api/tunnel.ts` -- `packages/server/src/api/client.ts` -- `packages/server/src/index.ts` - -The intended direction here is the same, but the current `opencode` package split is earlier in the migration. - -That means: - -- we should follow the same `definition` and `api` naming -- we should keep contract and implementation as separate modules from the start -- we should postpone the runtime composition root until `packages/core` exists enough to support it cleanly - -## Key decision - -Start `packages/server` as a contract and implementation package only. - -Do not make it the runtime host yet. - -Why: - -- `packages/core` does not exist yet -- the current server host still lives in `packages/opencode` -- moving host ownership immediately would force a large package and runtime shuffle while Effect service extraction is still in flight -- if `packages/server` imports services from `packages/opencode` while `packages/opencode` imports `packages/server` to host routes, we create a package cycle immediately - -Short version: - -1. create `packages/server` -2. move pure `HttpApi` contracts there -3. move handler factories there -4. keep `packages/opencode` as the temporary Hono host -5. merge `packages/server` OpenAPI with the legacy Hono OpenAPI during the transition -6. move server hosting later, after `packages/core` exists enough - -## Dependency rule - -Phase 1 rule: - -- `packages/server` must not import from `packages/opencode` - -Allowed in phase 1: - -- `packages/opencode` imports `packages/server` -- `packages/server` accepts host-provided services, layers, or callbacks as inputs -- `packages/server` may temporarily own transport-local placeholder schemas when a canonical shared schema does not exist yet - -Future rule after `packages/core` exists: - -- `packages/server` imports from `packages/core` -- `packages/cli` imports from `packages/server` and `packages/core` -- `packages/opencode` shrinks or disappears as package responsibilities are fully split - -## HttpApi model - -Use Effect v4 `HttpApi` as the source of truth for migrated HTTP routes. - -Important properties from the current `effect` / `effect-smol` model: - -- `HttpApi`, `HttpApiGroup`, and `HttpApiEndpoint` are pure contract definitions -- handlers are implemented separately with `HttpApiBuilder.group(...)` -- OpenAPI can be generated from the contract alone -- auth and middleware can later be modeled with `HttpApiMiddleware.Service` -- SSE and websocket routes are not good first-wave `HttpApi` targets - -This package split should preserve that separation explicitly. - -Default shape for migrated routes: - -- contract lives in `packages/server/src/definition/*` -- implementation lives in `packages/server/src/api/*` -- host mounting stays outside for now - -## OpenAPI rule - -During the transition there is still one spec artifact. - -Default rule: - -- `packages/server` generates OpenAPI from `HttpApi` contract -- `packages/opencode` keeps generating legacy OpenAPI from Hono routes -- the temporary exported server spec is a merged document -- `packages/sdk` continues consuming one `openapi.json` - -Merge safety rules: - -- fail on duplicate `path + method` -- fail on duplicate `operationId` -- prefer explicit summary, description, and operation ids on all new `HttpApi` endpoints - -Practical implication: - -- do not make the SDK consume two specs -- do not switch SDK generation to `packages/server` only until enough of the route surface has moved - -## Package shape - -Minimum viable `packages/server`: - -- `src/index.ts` -- `src/definition/index.ts` -- `src/definition/api.ts` -- `src/definition/question.ts` -- `src/api/index.ts` -- `src/api/question.ts` -- `src/openapi.ts` -- `src/bridge/hono.ts` -- `src/types.ts` - -Later additions, once there is enough real contract surface: - -- `src/api/client.ts` -- runtime composition in `src/index.ts` - -Suggested initial exports: - -- `api` -- `openapi` -- `questionApi` -- `makeQuestionHandler` - -Phase 1 responsibilities: - -- own pure API contracts -- own handler factories for migrated slices -- own contract-generated OpenAPI -- expose host adapters needed by `packages/opencode` - -Phase 1 non-goals: - -- do not own `listen()` -- do not own adapter selection -- do not own global server middleware -- do not own websocket or SSE transport -- do not own process bootstrapping for CLI entrypoints - -## Current source inventory - -These files matter for the first phase. - -Current host and route composition: - -- `src/server/server.ts` -- `src/server/control/index.ts` -- `src/server/routes/instance/index.ts` -- `src/server/middleware.ts` -- `src/server/adapter.bun.ts` -- `src/server/adapter.node.ts` - -Current bridged `HttpApi` slices: - -- `src/server/routes/instance/httpapi/question.ts` -- `src/server/routes/instance/httpapi/permission.ts` -- `src/server/routes/instance/httpapi/provider.ts` -- `src/server/routes/instance/httpapi/config.ts` -- `src/server/routes/instance/httpapi/project.ts` -- `src/server/routes/instance/httpapi/server.ts` - -Current OpenAPI flow: - -- `src/server/server.ts` via `Server.openapi()` -- `src/cli/cmd/generate.ts` -- `packages/sdk/js/script/build.ts` - -Current runtime and service layer: - -- `src/effect/app-runtime.ts` -- `src/effect/run-service.ts` - -## Ownership rules - -Move first into `packages/server`: - -- the experimental `question` `HttpApi` slice -- future `provider` and `config` JSON read slices -- any new `HttpApi` route groups -- transport-local OpenAPI generation for migrated routes - -Keep in `packages/opencode` for now: - -- `src/server/server.ts` -- `src/server/control/index.ts` -- `src/server/routes/**/*.ts` -- `src/server/middleware.ts` -- `src/server/adapter.*.ts` -- `src/effect/app-runtime.ts` -- `src/effect/run-service.ts` -- all Effect services until they move to `packages/core` - -## Placeholder schema rule - -`packages/core` is allowed to lag behind. - -Until shared canonical schemas move to `packages/core`: - -- prefer importing existing Effect Schema DTOs from current locations when practical -- if a route only needs a transport-local type and moving the canonical schema would create unrelated churn, allow a temporary server-local placeholder schema -- if a placeholder is introduced, leave a short note so it does not become permanent - -The default rule from `schema.md` still applies: - -- Effect Schema owns the type -- `.zod` is compatibility only -- avoid parallel hand-written Zod and Effect definitions for the same migrated route shape - -## Host boundary rule - -Until host ownership moves: - -- auth stays at the outer Hono app level -- compression stays at the outer Hono app level -- CORS stays at the outer Hono app level -- instance and workspace lookup stay at the current middleware layer -- `packages/server` handlers should assume the host already provided the right request context -- do not redesign host middleware just to land the package split - -This matches the current guidance in `http-api.md`: - -- keep auth outside the first parallel `HttpApi` slices -- keep instance lookup outside the first parallel `HttpApi` slices -- keep the first migrations transport-focused and semantics-preserving - -## Route selection rules - -Good early migration targets: - -- `question` -- `provider` auth read endpoint -- `config` providers read endpoint -- small read-only instance routes - -Bad early migration targets: - -- `session` -- `event` -- `pty` -- most `global` streaming or process-heavy routes -- anything requiring websocket upgrade handling -- anything that mixes many mutations and streaming in one file - -## First vertical slice - -The first slice for the package split is still the existing `question` `HttpApi` group. - -Why `question` first: - -- it already exists as an experimental `HttpApi` slice -- it already follows the desired contract and implementation split in one file -- it is already mounted through the current Hono host -- it is JSON-only -- it has low blast radius - -Use the first slice to prove: - -- package boundary -- contract and implementation split -- host mounting from `packages/opencode` -- merged OpenAPI output -- test ergonomics for future slices - -Do not broaden scope in the first slice. - -## Incremental migration order - -Use small PRs. - -Each PR should be easy to review, easy to revert, and should not mix extraction work with unrelated service refactors. - -### PR 1. Create `packages/server` - -Scope: - -- add the new workspace package -- add package manifest and tsconfig -- add empty `src/index.ts`, `src/definition/api.ts`, `src/definition/index.ts`, `src/api/index.ts`, `src/openapi.ts`, and supporting scaffolding - -Rules: - -- no production behavior changes -- no host server changes yet -- no imports from `packages/opencode` inside `packages/server` -- prefer `opentunnel`-style naming from the start: `definition` for contracts, `api` for implementations - -Done means: - -- `packages/server` typechecks -- the workspace can import it -- the package boundary is in place for follow-up PRs - -### PR 2. Move the experimental question contract - -Scope: - -- extract the pure `HttpApi` contract from `src/server/routes/instance/httpapi/question.ts` -- place it in `packages/server/src/definition/question.ts` -- aggregate it in `packages/server/src/definition/api.ts` -- generate OpenAPI in `packages/server/src/openapi.ts` - -Rules: - -- contract only in this PR -- no handler movement yet if that keeps the diff simpler -- keep operation ids and docs metadata stable - -Done means: - -- question contract lives in `packages/server` -- OpenAPI can be generated from contract alone -- no runtime behavior changes yet - -### PR 3. Move the experimental question handler factory - -Scope: - -- extract the question `HttpApiBuilder.group(...)` implementation into `packages/server/src/api/question.ts` -- expose it as a factory that accepts host-provided dependencies or wiring -- add a small Hono bridge in `packages/server/src/bridge/hono.ts` if needed - -Rules: - -- `packages/server` must still not import from `packages/opencode` -- handler code should stay thin and service-delegating -- do not redesign the question service itself in this PR - -Done means: - -- `packages/server` can produce the experimental question handler -- the package still stays cycle-free - -### PR 4. Mount `packages/server` question from `packages/opencode` - -Scope: - -- replace local experimental question route wiring in `packages/opencode` -- keep the same mount path: -- `/question` -- `/question/:requestID/reply` -- `/question/:requestID/reject` - -Rules: - -- no behavior change -- preserve existing docs path -- preserve current request and response shapes - -Done means: - -- existing question `HttpApi` test still passes -- runtime behavior is unchanged -- the current host server is now consuming `packages/server` - -### PR 5. Merge legacy and contract OpenAPI - -Scope: - -- keep `Server.openapi()` as the temporary spec entrypoint -- generate legacy Hono spec -- generate `packages/server` contract spec -- merge them into one document -- keep `cli/cmd/generate.ts` and `packages/sdk/js/script/build.ts` consuming one spec - -Rules: - -- fail loudly on duplicate `path + method` -- fail loudly on duplicate `operationId` -- do not silently overwrite one source with the other - -Done means: - -- one merged spec is produced -- migrated question paths can come from `packages/server` -- existing SDK generation path still works - -### PR 6. Add merged OpenAPI coverage - -Scope: - -- add one test for merged OpenAPI -- assert both a legacy Hono route and a migrated `HttpApi` route exist - -Rules: - -- test the merged document, not just the `packages/server` contract spec in isolation -- pick one stable legacy route and one stable migrated route - -Done means: - -- the merged-spec path is covered -- future route migrations have a guardrail - -### PR 7. Migrate `GET /provider/auth` - -Scope: - -- add `GET /provider/auth` as the next `HttpApi` slice in `packages/server` -- mount it in parallel from `packages/opencode` - -Why this route: - -- JSON-only -- simple service delegation -- small response shape -- already listed as the best next `provider` candidate in `http-api.md` - -Done means: - -- route works through the current host -- route appears in merged OpenAPI -- no semantic change to provider auth behavior - -### PR 8. Migrate `GET /config/providers` - -Scope: - -- add `GET /config/providers` as a `HttpApi` slice in `packages/server` -- mount it in parallel from `packages/opencode` - -Why this route: - -- JSON-only -- read-only -- low transport complexity -- already listed as the best next `config` candidate in `http-api.md` - -Done means: - -- route works unchanged -- route appears in merged OpenAPI - -### PR 9+. Migrate small read-only instance routes - -Candidate order: - -1. `GET /path` -2. `GET /vcs` -3. `GET /vcs/diff` -4. `GET /command` -5. `GET /agent` -6. `GET /skill` - -Rules: - -- one or two endpoints per PR -- prefer read-only routes first -- keep outer middleware unchanged -- keep business logic in the existing service layer - -Done means for each PR: - -- contract lives in `packages/server` -- handler lives in `packages/server` -- route is mounted from the current host -- route appears in merged OpenAPI -- behavior remains unchanged - -### Later PR. Move host ownership into `packages/server` - -Only start this after there is enough `packages/core` surface to depend on directly. - -Scope: - -- move server composition into `packages/server` -- add embeddable APIs such as `createServer(...)`, `listen(...)`, or `createApp(...)` -- move adapter selection and server startup out of `packages/opencode` - -Rules: - -- do not start this while `packages/server` still depends on `packages/opencode` -- do not mix this with route migration PRs - -Done means: - -- `packages/server` can be embedded in another Node app -- `packages/cli` can depend on `packages/server` -- host logic no longer lives in `packages/opencode` - -## PR sizing rule - -Every migration PR should satisfy all of these: - -- one route group or one to two endpoints -- no unrelated service refactor -- no auth redesign -- no middleware redesign -- OpenAPI updated -- at least one route test or spec test added or updated - -## Done means for a migrated route group - -A route group migration is complete only when: - -1. the `HttpApi` contract lives in `packages/server` -2. handler implementation lives in `packages/server` -3. the route is mounted from the current host in `packages/opencode` -4. the route appears in merged OpenAPI -5. request and response schemas are Effect Schema-first or clearly temporary placeholders -6. existing behavior remains unchanged -7. the route has straightforward test coverage - -## Validation expectations - -For package-split PRs, validate the smallest useful thing. - -Typical validation for the first waves: - -- `bun typecheck` in the touched package directory or directories -- the relevant server / route coverage for the migrated slice -- merged OpenAPI coverage if the PR touches spec generation - -Do not run tests from repo root. - -## Main risks - -### Package cycle - -This is the biggest risk. - -Bad state: - -- `packages/server` imports services or runtime from `packages/opencode` -- `packages/opencode` imports route definitions or handlers from `packages/server` - -Avoid by: - -- keeping phase-1 `packages/server` free of `packages/opencode` imports -- using factories and host-provided wiring instead of direct service imports - -### Spec drift - -During the transition there are two route-definition sources. - -Avoid by: - -- one merged spec -- collision checks -- explicit `operationId`s -- merged OpenAPI tests - -### Middleware mismatch - -Current auth, compression, CORS, and instance selection are Hono-centered. - -Avoid by: - -- leaving them where they are during the first wave -- not trying to solve `HttpApiMiddleware.Service` globally in the package-split PRs - -### Core lag - -`packages/core` will not be ready everywhere. - -Avoid by: - -- allowing small transport-local placeholder schemas where necessary -- keeping those placeholders clearly temporary -- not blocking the server extraction on full schema movement - -### Scope creep - -The first vertical slice is easy to overload. - -Avoid by: +- `packages/core` - shared domain services and schemas +- `packages/server` - HTTP contracts, handlers, OpenAPI generation, and an + embeddable server API +- `packages/cli` - TUI and CLI entrypoints +- `packages/sdk` - generated from the server OpenAPI spec +- `packages/plugin` - plugin authoring surface -- proving the package boundary first -- not mixing package creation, route migration, host redesign, and core extraction in the same change +## Extraction Rule -## Non-goals for the first wave +Do not create a package cycle. -- do not replace all Hono routes at once -- do not migrate SSE or websocket routes first -- do not redesign auth -- do not redesign instance lookup -- do not wait for full `packages/core` before starting `packages/server` -- do not change SDK generation to consume multiple specs +Until enough shared service code lives outside `packages/opencode`, a future +`packages/server` should either: -## Checklist +- own pure HttpApi contracts only, or +- accept host-provided services/layers/callbacks from `packages/opencode` -- [x] create `packages/server` -- [x] add package-level exports for contract and OpenAPI -- [ ] extract `question` contract into `packages/server` -- [ ] extract `question` handler factory into `packages/server` -- [ ] mount `question` from `packages/opencode` -- [ ] merge legacy and contract OpenAPI into one document -- [ ] add merged-spec coverage -- [ ] migrate `GET /provider/auth` -- [ ] migrate `GET /config/providers` -- [ ] migrate small read-only instance routes one or two at a time -- [ ] move host ownership into `packages/server` only after `packages/core` is ready enough -- [ ] split `packages/cli` after server and core boundaries are stable +It should not import `packages/opencode` services while `packages/opencode` +imports it to host routes. -## Rule of thumb +## Suggested PR Sequence -The fastest correct path is: +1. Keep shrinking OpenAPI compatibility shims in `httpapi/public.ts`. +2. Move stable domain schemas into shared packages only when they no longer + depend on opencode-local runtime modules. +3. Extract pure HttpApi contract modules into `packages/server` once the contract + can compile without importing `packages/opencode` implementation details. +4. Extract handler factories after their service dependencies can be supplied by + a host layer instead of imported directly. +5. Move server hosting last, after package ownership is clear. -1. establish `packages/server` as the contract-first boundary -2. keep `packages/opencode` as the temporary host -3. migrate a few safe JSON routes -4. keep one merged OpenAPI document -5. move actual host ownership only after `packages/core` can support it cleanly +## Non-Goals -If a proposed PR would make `packages/server` import from `packages/opencode`, stop and restructure the boundary first. +- Do not revive the old dual-backend migration shape. +- Do not split server hosting before service dependencies have a clean package + boundary. +- Do not switch SDK generation to a new package until generated output is known + to remain compatible. diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts index 7027e666c..b9d5b5af1 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts @@ -61,7 +61,7 @@ export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "provider" const payload = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ProviderAuth.AuthorizeInput))(body).pipe( Effect.mapError(() => new HttpApiError.BadRequest({})), ) - // Match legacy Hono behavior: when authorize() resolves without a + // Match legacy route behavior: when authorize() resolves without a // result (e.g. no further redirect), serialize as JSON `null` instead // of an empty body so clients can `.json()` parse the response. const result = yield* authorize({ params: ctx.params, payload }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts index 369ca91d0..f4d6adb93 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/pty.ts @@ -153,7 +153,7 @@ export const ptyConnectRoute = HttpRouter.use((router) => return HttpServerResponse.empty() } - // No `pending[]`-style early-frame buffer (the legacy Hono handler had one). + // No `pending[]`-style early-frame buffer (the legacy handler had one). // `request.upgrade` returns a Socket without running the WS handshake; the // handshake fires inside `socket.runRaw` below, AFTER `pty.connect` resolves // and the message callback is registered. The client therefore can't fire diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 9230a6fe5..99645f3da 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -234,7 +234,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", // share/unshare errors aren't all client-induced — storage and network // failures from SessionShare are real possibilities. Map to a typed 500 - // (matches the legacy Hono path which routed any failure through + // (matches the legacy route behavior which routed any failure through // ErrorMiddleware → NamedError.Unknown 500) instead of blanket-mapping // every failure to a 400 BadRequest. const share = Effect.fn("SessionHttpApi.share")(function* (ctx: { params: { sessionID: SessionID } }) { diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/compression.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/compression.ts index 9dc9bc01e..9187bfea3 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/compression.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/compression.ts @@ -2,7 +2,7 @@ import { deflateSync, gzipSync } from "node:zlib" import { Effect } from "effect" import { HttpBody, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" -// Mirror of Hono's compressible content-type set so wire behavior matches. +// Keep the server's compressible content-type set stable across HTTP backend changes. const COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i diff --git a/packages/opencode/src/server/routes/instance/httpapi/public.ts b/packages/opencode/src/server/routes/instance/httpapi/public.ts index 460a2be7a..12d3791ec 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/public.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/public.ts @@ -111,8 +111,8 @@ function matchLegacyOpenApi(input: Record) { const operation = item[method] if (!operation) continue if (operation.requestBody) { - // Hono's generated OpenAPI never marked request bodies as required. Keep - // that SDK surface stable during the HttpApi migration. + // The legacy OpenAPI surface never marked request bodies as required. + // Keep that SDK surface stable while the HttpApi spec is tightened. delete operation.requestBody.required const body = operation.requestBody.content?.["application/json"] if (body?.schema) body.schema = stripOptionalNull(structuredClone(body.schema)) @@ -146,8 +146,8 @@ function matchLegacyOpenApi(input: Record) { if (content.schema) content.schema = stripOptionalNull(structuredClone(content.schema)) } } - // Hono applied auth as runtime middleware outside OpenAPI metadata, so the - // legacy SDK did not expose auth schemes or generated 401 error unions. + // Auth is still runtime middleware outside the public OpenAPI metadata, so + // the SDK should not expose auth schemes or generated 401 error unions. delete operation.security delete operation.responses?.["401"] normalizeLegacyErrorResponses(operation) diff --git a/packages/opencode/test/project/instance-bootstrap.test.ts b/packages/opencode/test/project/instance-bootstrap.test.ts index baad8df59..71521a765 100644 --- a/packages/opencode/test/project/instance-bootstrap.test.ts +++ b/packages/opencode/test/project/instance-bootstrap.test.ts @@ -13,9 +13,7 @@ import { disposeAllInstances, tmpdir } from "../fixture/fixture" // bodies deliberately avoid Plugin/config directly. The marker only // appears if InstanceBootstrap ran at the instance boundary. // -// The Hono variant of this check lived alongside these tests and is -// going away with the Hono backend. The boundaries below are backend- -// agnostic and stay. +// The boundaries below are transport-agnostic and stay. afterEach(async () => { await disposeAllInstances() diff --git a/packages/opencode/test/server/httpapi-config.test.ts b/packages/opencode/test/server/httpapi-config.test.ts index 509a067d0..26c0fe03e 100644 --- a/packages/opencode/test/server/httpapi-config.test.ts +++ b/packages/opencode/test/server/httpapi-config.test.ts @@ -25,7 +25,7 @@ afterEach(async () => { }) describe("config HttpApi", () => { - test("serves config update through Hono bridge", async () => { + test("serves config update through the default server app", async () => { await using tmp = await tmpdir({ config: { formatter: false, lsp: false } }) const disposed = waitDisposed(tmp.path) diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index 0b8d8051b..383442e00 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -1,6 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test" import { Effect } from "effect" -import { Instance } from "../../src/project/instance" import { WithInstance } from "../../src/project/with-instance" import { Server } from "../../src/server/server" import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/groups/experimental" @@ -41,7 +40,7 @@ afterEach(async () => { }) describe("experimental HttpApi", () => { - test("serves read-only experimental endpoints through Hono bridge", async () => { + test("serves read-only experimental endpoints through the default server app", async () => { await using tmp = await tmpdir({ config: { formatter: false, @@ -94,7 +93,7 @@ describe("experimental HttpApi", () => { expect(await resources.json()).toEqual({}) }) - test("serves Console org switch through Hono bridge", async () => { + test("serves Console org switch through the default server app", async () => { await using tmp = await tmpdir({ config: { formatter: false, lsp: false } }) Database.Client() .$client.prepare( @@ -120,7 +119,7 @@ describe("experimental HttpApi", () => { expect(await switched.json()).toBe(true) }) - test("serves global session list through Hono bridge", async () => { + test("serves global session list through the default server app", async () => { await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) const first = await WithInstance.provide({ @@ -157,7 +156,7 @@ describe("experimental HttpApi", () => { expect(((await next.json()) as Session.GlobalInfo[]).map((session) => session.id)).toContain(first.id) }) - testWorktreeMutations("serves worktree mutations through Hono bridge", async () => { + testWorktreeMutations("serves worktree mutations through the default server app", async () => { await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } diff --git a/packages/opencode/test/server/httpapi-instance.test.ts b/packages/opencode/test/server/httpapi-instance.test.ts index 946de2835..5ec4acb87 100644 --- a/packages/opencode/test/server/httpapi-instance.test.ts +++ b/packages/opencode/test/server/httpapi-instance.test.ts @@ -11,7 +11,7 @@ import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/se import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { HEADER as FenceHeader } from "../../src/server/shared/fence" import { resetDatabase } from "../fixture/db" -import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture" +import { tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" // Flip the experimental workspaces flag so SyncEvent.run actually writes to @@ -35,8 +35,7 @@ const testStateLayer = Layer.effectDiscard( // Mount the production HttpApi route tree on a real Node HTTP server bound to // 127.0.0.1:0 and a fetch-based HttpClient that prepends the server URL. This -// keeps the test wired through the same route layer production uses, without -// going through Server.Default()/Hono. +// keeps the test wired directly through the same route layer production uses. const servedRoutes: Layer.Layer = HttpRouter.serve( ExperimentalHttpApiServer.routes, { disableListenLog: true, disableLogger: true }, diff --git a/specs/v2/todo.md b/specs/v2/todo.md index 77c650e55..ca38931f8 100644 --- a/specs/v2/todo.md +++ b/specs/v2/todo.md @@ -2,9 +2,11 @@ ok we need to work towards a launch of v2 so we can get out of this rebuild phase -## Kill Hono - Kit +## Post-Hono cleanup - Kit -Hono needs to go away so zod can go away. this is almost done +The opencode server has moved to the Effect HttpApi backend. Remaining work is +mostly cleanup: delete compatibility shims, shrink Zod surfaces, and simplify +test harnesses that used to compare Hono and HttpApi behavior. ## New Data Mode - Dax From c7e084c32ca74284b75d6e11e1add0f8a51b99e9 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 13:22:31 -0400 Subject: [PATCH 034/378] Simplify single-backend HttpApi exerciser (#26906) --- .../test/server/httpapi-exercise/backend.ts | 23 ++++++------------ .../test/server/httpapi-exercise/runner.ts | 24 +++++++++---------- .../test/server/httpapi-exercise/types.ts | 1 - 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/packages/opencode/test/server/httpapi-exercise/backend.ts b/packages/opencode/test/server/httpapi-exercise/backend.ts index fac5f699c..b306401cc 100644 --- a/packages/opencode/test/server/httpapi-exercise/backend.ts +++ b/packages/opencode/test/server/httpapi-exercise/backend.ts @@ -2,7 +2,7 @@ import { ConfigProvider, Effect, Layer } from "effect" import { HttpRouter } from "effect/unstable/http" import { parse } from "./assertions" import { runtime, type Runtime } from "./runtime" -import type { ActiveScenario, Backend, BackendApp, CallResult, CaptureMode, SeededContext } from "./types" +import type { ActiveScenario, BackendApp, CallResult, CaptureMode, SeededContext } from "./types" type CallOptions = { auth?: { @@ -11,27 +11,18 @@ type CallOptions = { } } -export function call( - backend: Backend, - scenario: ActiveScenario, - ctx: SeededContext, - options: CallOptions = {}, -) { +export function call(scenario: ActiveScenario, ctx: SeededContext, options: CallOptions = {}) { return Effect.promise(async () => - capture(await app(await runtime(), backend, options).request(toRequest(scenario, ctx)), scenario.capture), + capture(await app(await runtime(), options).request(toRequest(scenario, ctx)), scenario.capture), ) } -export function callAuthProbe( - backend: Backend, - scenario: ActiveScenario, - credentials: "missing" | "valid" = "missing", -) { +export function callAuthProbe(scenario: ActiveScenario, credentials: "missing" | "valid" = "missing") { return Effect.promise(async () => { const controller = new AbortController() return Promise.race([ Promise.resolve( - app(await runtime(), backend, { auth: { password: "secret" } }).request( + app(await runtime(), { auth: { password: "secret" } }).request( toAuthProbeRequest(scenario, credentials, controller.signal), ), ).then((response) => capture(response, scenario.capture)), @@ -51,10 +42,10 @@ export function callAuthProbe( const appCache: Partial> = {} -function app(modules: Runtime, backend: Backend, options: CallOptions) { +function app(modules: Runtime, options: CallOptions) { const username = options.auth?.username const password = options.auth?.password - const cacheKey = `${backend}:${username ?? ""}:${password ?? ""}` + const cacheKey = `${username ?? ""}:${password ?? ""}` if (appCache[cacheKey]) return appCache[cacheKey] const handler = HttpRouter.toWebHandler( diff --git a/packages/opencode/test/server/httpapi-exercise/runner.ts b/packages/opencode/test/server/httpapi-exercise/runner.ts index 2b3f720c8..bc246dbed 100644 --- a/packages/opencode/test/server/httpapi-exercise/runner.ts +++ b/packages/opencode/test/server/httpapi-exercise/runner.ts @@ -30,28 +30,28 @@ function runActive(options: Options, scenario: ActiveScenario) { return withContext(options, scenario, "shared", (ctx) => Effect.gen(function* () { - yield* trace(options, scenario, "effect request start") - const effect = yield* call("effect", scenario, ctx) - yield* trace(options, scenario, `effect response ${effect.status}`) - yield* trace(options, scenario, "effect expect start") - yield* scenario.expect(ctx, ctx.state, effect) - yield* trace(options, scenario, "effect expect done") + yield* trace(options, scenario, "request start") + const result = yield* call(scenario, ctx) + yield* trace(options, scenario, `response ${result.status}`) + yield* trace(options, scenario, "expect start") + yield* scenario.expect(ctx, ctx.state, result) + yield* trace(options, scenario, "expect done") }), ) } function runAuth(scenario: ActiveScenario) { return Effect.gen(function* () { - const effect = yield* callAuthProbe("effect", scenario, "missing") + const result = yield* callAuthProbe(scenario, "missing") if (scenario.auth === "protected") { - if (effect.status !== 401) throw new Error(`effect auth expected 401, got ${effect.status}`) - const effectAuthed = yield* callAuthProbe("effect", scenario, "valid") - if (effectAuthed.status === 401) throw new Error("effect auth rejected valid credentials") + if (result.status !== 401) throw new Error(`auth expected 401, got ${result.status}`) + const authed = yield* callAuthProbe(scenario, "valid") + if (authed.status === 401) throw new Error("auth rejected valid credentials") return } - if (effect.status === 401) throw new Error("effect auth expected public access, got 401") - if (effect.timedOut) throw new Error("effect auth expected public access, probe timed out") + if (result.status === 401) throw new Error("auth expected public access, got 401") + if (result.timedOut) throw new Error("auth expected public access, probe timed out") }) } diff --git a/packages/opencode/test/server/httpapi-exercise/types.ts b/packages/opencode/test/server/httpapi-exercise/types.ts index a0466d7b7..e1fe93ba7 100644 --- a/packages/opencode/test/server/httpapi-exercise/types.ts +++ b/packages/opencode/test/server/httpapi-exercise/types.ts @@ -11,7 +11,6 @@ export const Methods = ["GET", "POST", "PUT", "DELETE", "PATCH"] as const export type Method = (typeof Methods)[number] export type OpenApiMethod = (typeof OpenApiMethods)[number] export type Mode = "effect" | "coverage" | "auth" -export type Backend = "effect" export type Comparison = "none" | "status" | "json" export type CaptureMode = "full" | "stream" export type AuthPolicy = "protected" | "public" | "public-bypass" | "ticket-bypass" From 8d9b9719bedadb5289c678eb61c4c4b5fdd1681f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 13:22:59 -0400 Subject: [PATCH 035/378] Drop unused small ID Zod statics (#26908) --- packages/opencode/src/permission/schema.ts | 3 --- packages/opencode/src/project/schema.ts | 2 -- packages/opencode/src/pty/schema.ts | 2 -- packages/opencode/src/question/schema.ts | 3 --- packages/opencode/src/sync/schema.ts | 2 -- packages/opencode/test/session/schema-decoding.test.ts | 2 +- 6 files changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/opencode/src/permission/schema.ts b/packages/opencode/src/permission/schema.ts index f7c6e2c5b..58ef0a8a7 100644 --- a/packages/opencode/src/permission/schema.ts +++ b/packages/opencode/src/permission/schema.ts @@ -1,7 +1,6 @@ import { Schema } from "effect" import { Identifier } from "@/id/id" -import { zod } from "@opencode-ai/core/effect-zod" import { Newtype } from "@opencode-ai/core/schema" export class PermissionID extends Newtype()( @@ -11,6 +10,4 @@ export class PermissionID extends Newtype()( static ascending(id?: string): PermissionID { return this.make(Identifier.ascending("permission", id)) } - - static readonly zod = zod(this) } diff --git a/packages/opencode/src/project/schema.ts b/packages/opencode/src/project/schema.ts index c6cff94fd..e511a75ff 100644 --- a/packages/opencode/src/project/schema.ts +++ b/packages/opencode/src/project/schema.ts @@ -1,6 +1,5 @@ import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" import { withStatics } from "@opencode-ai/core/schema" const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectID")) @@ -10,6 +9,5 @@ export type ProjectID = typeof projectIdSchema.Type export const ProjectID = projectIdSchema.pipe( withStatics((schema: typeof projectIdSchema) => ({ global: schema.make("global"), - zod: zod(schema), })), ) diff --git a/packages/opencode/src/pty/schema.ts b/packages/opencode/src/pty/schema.ts index fadb0457e..c86ae8c73 100644 --- a/packages/opencode/src/pty/schema.ts +++ b/packages/opencode/src/pty/schema.ts @@ -1,7 +1,6 @@ import { Schema } from "effect" import { Identifier } from "@/id/id" -import { zod } from "@opencode-ai/core/effect-zod" import { withStatics } from "@opencode-ai/core/schema" const ptyIdSchema = Schema.String.check(Schema.isStartsWith("pty")).pipe(Schema.brand("PtyID")) @@ -11,6 +10,5 @@ export type PtyID = typeof ptyIdSchema.Type export const PtyID = ptyIdSchema.pipe( withStatics((schema: typeof ptyIdSchema) => ({ ascending: (id?: string) => schema.make(Identifier.ascending("pty", id)), - zod: zod(schema), })), ) diff --git a/packages/opencode/src/question/schema.ts b/packages/opencode/src/question/schema.ts index 1856c94bc..2574594a2 100644 --- a/packages/opencode/src/question/schema.ts +++ b/packages/opencode/src/question/schema.ts @@ -1,13 +1,10 @@ import { Schema } from "effect" import { Identifier } from "@/id/id" -import { zod } from "@opencode-ai/core/effect-zod" import { Newtype } from "@opencode-ai/core/schema" export class QuestionID extends Newtype()("QuestionID", Schema.String.check(Schema.isStartsWith("que"))) { static ascending(id?: string): QuestionID { return this.make(Identifier.ascending("question", id)) } - - static readonly zod = zod(this) } diff --git a/packages/opencode/src/sync/schema.ts b/packages/opencode/src/sync/schema.ts index dde2e53d1..4ff9d2fc3 100644 --- a/packages/opencode/src/sync/schema.ts +++ b/packages/opencode/src/sync/schema.ts @@ -1,13 +1,11 @@ import { Schema } from "effect" import { Identifier } from "@/id/id" -import { zod } from "@opencode-ai/core/effect-zod" import { withStatics } from "@opencode-ai/core/schema" export const EventID = Schema.String.check(Schema.isStartsWith("evt")).pipe( Schema.brand("EventID"), withStatics((s) => ({ ascending: (id?: string) => s.make(Identifier.ascending("event", id)), - zod: zod(s), })), ) diff --git a/packages/opencode/test/session/schema-decoding.test.ts b/packages/opencode/test/session/schema-decoding.test.ts index 67c438a38..a65137a2f 100644 --- a/packages/opencode/test/session/schema-decoding.test.ts +++ b/packages/opencode/test/session/schema-decoding.test.ts @@ -27,7 +27,7 @@ const sessionID = Schema.decodeUnknownSync(SessionID)("ses_01J5Y5H0AH4Q4NXJ6P4C3 const sessionIDChild = Schema.decodeUnknownSync(SessionID)("ses_01J5Y5H0AH4Q4NXJ6P4C3P5V2L") const messageID = Schema.decodeUnknownSync(MessageID)("msg_01J5Y5H0AH4Q4NXJ6P4C3P5V2M") const partID = Schema.decodeUnknownSync(PartID)("prt_01J5Y5H0AH4Q4NXJ6P4C3P5V2N") -const projectID = ProjectID.zod.parse("proj-alpha") +const projectID = ProjectID.make("proj-alpha") const workspaceID = Schema.decodeUnknownSync(WorkspaceID)("wrk-primary") function decodeUnknown(schema: S) { From 0bced8ec966878477641ccbe4666cdb274667a44 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 11 May 2026 17:32:56 +0000 Subject: [PATCH 036/378] 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 4244e0c0e..9127bb6d3 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-baGxh+hk/rPhg0xI/OdMDz6dPwncgercYNBdTPnLX9o=", - "aarch64-linux": "sha256-VTWKq679B3Q4ZnAoQzC4VSCYA09wWecNJ+JajvjNB1U=", - "aarch64-darwin": "sha256-orf2zIBMTiiQrt/6qCzE+o0oKhv6u8zXF9DH1Bo3lbo=", - "x86_64-darwin": "sha256-1MZC1fadRoY4lhkmjlcUQTLYH9Q8pDI1bxd5f94f1xU=" + "x86_64-linux": "sha256-2rWGrfNuMLayiouyK65Bu7cSiE0WZIyckU/PChZeYLU=", + "aarch64-linux": "sha256-yF+0UfYCb1isfuAg/KVTJcNxK2yt2gz0o7h8F8WK994=", + "aarch64-darwin": "sha256-KU2/HtyKOAKqJUnqgEyCe5XhYnoUZxqlQtW814yFZKs=", + "x86_64-darwin": "sha256-D/RD/HjMMg2r1HD/glnG+y7DnVEE7guA/J4oiYOAqqc=" } } From 45adfedd6412b173a9a13092ee6914c98d1b2107 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 16:10:58 -0400 Subject: [PATCH 037/378] Drop unused domain Zod statics (#26927) --- packages/opencode/src/agent/agent.ts | 7 ++----- packages/opencode/src/file/index.ts | 15 ++++----------- packages/opencode/src/file/ripgrep.ts | 5 ++--- packages/opencode/src/format/index.ts | 6 +----- packages/opencode/src/pty/index.ts | 11 ++++------- packages/opencode/src/skill/index.ts | 4 +--- packages/opencode/src/snapshot/index.ts | 9 ++------- 7 files changed, 16 insertions(+), 41 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 777f6e6d1..d96e508c9 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -24,8 +24,7 @@ import { Effect, Context, Layer, Schema } from "effect" import { InstanceState } from "@/effect/instance-state" import * as Option from "effect/Option" import * as OtelTracer from "@effect/opentelemetry/Tracer" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics, type DeepMutable } from "@opencode-ai/core/schema" +import { type DeepMutable } from "@opencode-ai/core/schema" export const Info = Schema.Struct({ name: Schema.String, @@ -47,9 +46,7 @@ export const Info = Schema.Struct({ prompt: Schema.optional(Schema.String), options: Schema.Record(Schema.String, Schema.Unknown), steps: Schema.optional(Schema.Finite), -}) - .annotate({ identifier: "Agent" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Agent" }) export type Info = DeepMutable> export interface Interface { diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 52f2b8486..b951a4d7a 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -14,17 +14,14 @@ import { containsPath } from "../project/instance-context" import * as Log from "@opencode-ai/core/util/log" import { Protected } from "./protected" import { Ripgrep } from "./ripgrep" -import { zod } from "@opencode-ai/core/effect-zod" -import { NonNegativeInt, type DeepMutable, withStatics } from "@opencode-ai/core/schema" +import { NonNegativeInt, type DeepMutable } from "@opencode-ai/core/schema" export const Info = Schema.Struct({ path: Schema.String, added: NonNegativeInt, removed: NonNegativeInt, status: Schema.Literals(["added", "deleted", "modified"]), -}) - .annotate({ identifier: "File" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "File" }) export type Info = DeepMutable> export const Node = Schema.Struct({ @@ -33,9 +30,7 @@ export const Node = Schema.Struct({ absolute: Schema.String, type: Schema.Literals(["file", "directory"]), ignored: Schema.Boolean, -}) - .annotate({ identifier: "FileNode" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "FileNode" }) export type Node = DeepMutable> const Hunk = Schema.Struct({ @@ -62,9 +57,7 @@ export const Content = Schema.Struct({ patch: Schema.optional(Patch), encoding: Schema.optional(Schema.Literal("base64")), mimeType: Schema.optional(Schema.String), -}) - .annotate({ identifier: "FileContent" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "FileContent" }) export type Content = DeepMutable> export const Event = { diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 8459dd9ac..aae794c1a 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -11,8 +11,7 @@ import { Global } from "@opencode-ai/core/global" import * as Log from "@opencode-ai/core/util/log" import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" import { which } from "@/util/which" -import { zod } from "@opencode-ai/core/effect-zod" -import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema" +import { NonNegativeInt } from "@opencode-ai/core/schema" const log = Log.create({ service: "ripgrep" }) const VERSION = "15.1.0" @@ -69,7 +68,7 @@ export const SearchMatch = Schema.Struct({ end: NonNegativeInt, }), ), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export const Match = Schema.Struct({ type: Schema.Literal("match"), diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index c9ab433f1..b6eb9dfd0 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -7,8 +7,6 @@ import { mergeDeep } from "remeda" import { Config } from "@/config/config" import * as Log from "@opencode-ai/core/util/log" import * as Formatter from "./formatter" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" const log = Log.create({ service: "format" }) @@ -16,9 +14,7 @@ export const Status = Schema.Struct({ name: Schema.String, extensions: Schema.Array(Schema.String), enabled: Schema.Boolean, -}) - .annotate({ identifier: "FormatterStatus" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "FormatterStatus" }) export type Status = Schema.Schema.Type export interface Interface { diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 85e0840cb..6f18856fd 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -10,8 +10,7 @@ import type { Proc } from "#pty" import * as Log from "@opencode-ai/core/util/log" import { PtyID } from "./schema" import { Effect, Layer, Context, Schema, Types } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { NonNegativeInt, PositiveInt, withStatics } from "@opencode-ai/core/schema" +import { NonNegativeInt, PositiveInt } from "@opencode-ai/core/schema" const log = Log.create({ service: "pty" }) @@ -62,9 +61,7 @@ export const Info = Schema.Struct({ cwd: Schema.String, status: Schema.Literals(["running", "exited"]), pid: PositiveInt, -}) - .annotate({ identifier: "Pty" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Pty" }) export type Info = Types.DeepMutable> @@ -74,7 +71,7 @@ export const CreateInput = Schema.Struct({ cwd: Schema.optional(Schema.String), title: Schema.optional(Schema.String), env: Schema.optional(Schema.Record(Schema.String, Schema.String)), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type CreateInput = Types.DeepMutable> @@ -86,7 +83,7 @@ export const UpdateInput = Schema.Struct({ cols: PositiveInt, }), ), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type UpdateInput = Types.DeepMutable> diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index e532efa3d..a0cc383d0 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -2,8 +2,6 @@ import path from "path" import { pathToFileURL } from "url" import z from "zod" import { Effect, Layer, Context, Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" import { NamedError } from "@opencode-ai/core/util/error" import type { Agent } from "@/agent/agent" import { Bus } from "@/bus" @@ -40,7 +38,7 @@ export const Info = Schema.Struct({ description: Schema.optional(Schema.String), location: Schema.String, content: Schema.String, -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type Info = Schema.Schema.Type export const InvalidError = NamedError.create( diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 848a067c3..51fd267d5 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -2,7 +2,6 @@ import { Cause, Duration, Effect, Layer, Schedule, Schema, Semaphore, Context, S import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" -import z from "zod" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect/instance-state" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -10,13 +9,11 @@ import { Hash } from "@opencode-ai/core/util/hash" import { Config } from "@/config/config" import { Global } from "@opencode-ai/core/global" import * as Log from "@opencode-ai/core/util/log" -import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema" -import { zod } from "@opencode-ai/core/effect-zod" export const Patch = Schema.Struct({ hash: Schema.String, files: Schema.mutable(Schema.Array(Schema.String)), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type Patch = typeof Patch.Type export const FileDiff = Schema.Struct({ @@ -28,9 +25,7 @@ export const FileDiff = Schema.Struct({ additions: Schema.Finite, deletions: Schema.Finite, status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])), -}) - .annotate({ identifier: "SnapshotFileDiff" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "SnapshotFileDiff" }) export type FileDiff = typeof FileDiff.Type const log = Log.create({ service: "snapshot" }) From c060c436b6f1447be09116f36b77633e6eb7d89d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 16:11:11 -0400 Subject: [PATCH 038/378] Drop LSP config Zod statics (#26920) --- packages/opencode/src/config/lsp.ts | 8 +++----- packages/opencode/test/config/lsp.test.ts | 18 ------------------ 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/packages/opencode/src/config/lsp.ts b/packages/opencode/src/config/lsp.ts index accfbee3b..ea7328a80 100644 --- a/packages/opencode/src/config/lsp.ts +++ b/packages/opencode/src/config/lsp.ts @@ -1,13 +1,11 @@ export * as ConfigLSP from "./lsp" import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" import * as LSPServer from "../lsp/server" export const Disabled = Schema.Struct({ disabled: Schema.Literal(true), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}).pipe((schema) => schema) export const Entry = Schema.Union([ Disabled, @@ -18,7 +16,7 @@ export const Entry = Schema.Union([ env: Schema.optional(Schema.Record(Schema.String, Schema.String)), initialization: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), }), -]).pipe(withStatics((s) => ({ zod: zod(s) }))) +]).pipe((schema) => schema) /** * For custom (non-builtin) LSP server entries, `extensions` is required so the @@ -40,6 +38,6 @@ export const requiresExtensionsForCustomServers = Schema.makeFilter< export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)]) .check(requiresExtensionsForCustomServers) - .pipe(withStatics((s) => ({ zod: zod(s) }))) + .pipe((schema) => schema) export type Info = Schema.Schema.Type diff --git a/packages/opencode/test/config/lsp.test.ts b/packages/opencode/test/config/lsp.test.ts index 1d24fe124..ff0048a19 100644 --- a/packages/opencode/test/config/lsp.test.ts +++ b/packages/opencode/test/config/lsp.test.ts @@ -7,10 +7,6 @@ import { ConfigLSP } from "../../src/config/lsp" // the server should attach to. Builtin server IDs and explicitly disabled // entries are exempt. // -// Both validation paths must honor this rule: -// - `Schema.decodeUnknownSync(ConfigLSP.Info)` (Effect layer) -// - `ConfigLSP.Info.zod.parse(...)` (derived Zod) -// // `typescript` is a builtin server id (see src/lsp/server.ts). describe("ConfigLSP.Info refinement", () => { const decodeEffect = Schema.decodeUnknownSync(ConfigLSP.Info) @@ -19,14 +15,11 @@ describe("ConfigLSP.Info refinement", () => { test("true and false pass (top-level toggle)", () => { expect(decodeEffect(true)).toBe(true) expect(decodeEffect(false)).toBe(false) - expect(ConfigLSP.Info.zod.parse(true)).toBe(true) - expect(ConfigLSP.Info.zod.parse(false)).toBe(false) }) test("builtin server with no extensions passes", () => { const input = { typescript: { command: ["typescript-language-server", "--stdio"] } } expect(decodeEffect(input)).toEqual(input) - expect(ConfigLSP.Info.zod.parse(input)).toEqual(input) }) test("custom server WITH extensions passes", () => { @@ -34,13 +27,11 @@ describe("ConfigLSP.Info refinement", () => { "my-lsp": { command: ["my-lsp-bin"], extensions: [".ml"] }, } expect(decodeEffect(input)).toEqual(input) - expect(ConfigLSP.Info.zod.parse(input)).toEqual(input) }) test("disabled custom server passes (no extensions needed)", () => { const input = { "my-lsp": { disabled: true as const } } expect(decodeEffect(input)).toEqual(input) - expect(ConfigLSP.Info.zod.parse(input)).toEqual(input) }) test("mix of builtin and custom with extensions passes", () => { @@ -49,7 +40,6 @@ describe("ConfigLSP.Info refinement", () => { "my-lsp": { command: ["my-lsp-bin"], extensions: [".ml"] }, } expect(decodeEffect(input)).toEqual(input) - expect(ConfigLSP.Info.zod.parse(input)).toEqual(input) }) }) @@ -60,19 +50,12 @@ describe("ConfigLSP.Info refinement", () => { expect(() => decodeEffect({ "my-lsp": { command: ["my-lsp-bin"] } })).toThrow(expectedMessage) }) - test("custom server WITHOUT extensions fails via derived Zod", () => { - const result = ConfigLSP.Info.zod.safeParse({ "my-lsp": { command: ["my-lsp-bin"] } }) - expect(result.success).toBe(false) - expect(result.error!.issues.some((i) => i.message === expectedMessage)).toBe(true) - }) - test("custom server with empty extensions array fails (extensions must be non-empty-truthy)", () => { // Boolean(['']) is true, so a non-empty array of strings is fine. // Boolean([]) is also true in JS, so empty arrays are accepted by the // refinement. This test documents current behavior. const input = { "my-lsp": { command: ["my-lsp-bin"], extensions: [] } } expect(decodeEffect(input)).toEqual(input) - expect(ConfigLSP.Info.zod.parse(input)).toEqual(input) }) test("custom server without extensions mixed with a valid builtin still fails", () => { @@ -81,7 +64,6 @@ describe("ConfigLSP.Info refinement", () => { "my-lsp": { command: ["my-lsp-bin"] }, } expect(() => decodeEffect(input)).toThrow(expectedMessage) - expect(ConfigLSP.Info.zod.safeParse(input).success).toBe(false) }) }) }) From 4d9eb6c320429d0d70ee91419ada9a7c86d7a817 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 16:11:25 -0400 Subject: [PATCH 039/378] Validate structured output tests with Effect Schema (#26919) --- packages/opencode/src/session/message-v2.ts | 23 ++----- .../test/session/structured-output.test.ts | 65 ++++++++++--------- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index e3539021b..25ca27460 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -64,24 +64,19 @@ export const ContextOverflowError = namedSchemaError("ContextOverflowError", { export class OutputFormatText extends Schema.Class("OutputFormatText")({ type: Schema.Literal("text"), -}) { - static readonly zod = zod(this) -} +}) {} export class OutputFormatJsonSchema extends Schema.Class("OutputFormatJsonSchema")({ type: Schema.Literal("json_schema"), schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }), retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), -}) { - static readonly zod = zod(this) -} +}) {} -const _Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({ +export const Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({ discriminator: "type", identifier: "OutputFormat", }) -export const Format = Object.assign(_Format, { zod: zod(_Format) }) -export type OutputFormat = Schema.Schema.Type +export type OutputFormat = Schema.Schema.Type const partBase = { id: PartID, @@ -381,7 +376,7 @@ export const User = Schema.Struct({ time: Schema.Struct({ created: NonNegativeInt, }), - format: Schema.optional(_Format), + format: Schema.optional(Format), summary: Schema.optional( Schema.Struct({ title: Schema.optional(Schema.String), @@ -397,9 +392,7 @@ export const User = Schema.Struct({ }), system: Schema.optional(Schema.String), tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), -}) - .annotate({ identifier: "UserMessage" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "UserMessage" }) export type User = Types.DeepMutable> export const Part = Schema.Union([ @@ -550,9 +543,7 @@ export const Assistant = Schema.Struct({ structured: Schema.optional(Schema.Any), variant: Schema.optional(Schema.String), finish: Schema.optional(Schema.String), -}) - .annotate({ identifier: "AssistantMessage" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "AssistantMessage" }) export type Assistant = Omit>, "error"> & { error?: AssistantError } diff --git a/packages/opencode/test/session/structured-output.test.ts b/packages/opencode/test/session/structured-output.test.ts index c734a182a..806c57483 100644 --- a/packages/opencode/test/session/structured-output.test.ts +++ b/packages/opencode/test/session/structured-output.test.ts @@ -1,60 +1,65 @@ import { describe, expect, test } from "bun:test" +import { Exit, Schema } from "effect" import { MessageV2 } from "../../src/session/message-v2" import { SessionPrompt } from "../../src/session/prompt" import { SessionID, MessageID } from "../../src/session/schema" +const decodeFormat = Schema.decodeUnknownExit(MessageV2.Format) +const decodeUser = Schema.decodeUnknownExit(MessageV2.User) +const decodeAssistant = Schema.decodeUnknownExit(MessageV2.Assistant) + describe("structured-output.OutputFormat", () => { test("parses text format", () => { - const result = MessageV2.Format.zod.safeParse({ type: "text" }) - expect(result.success).toBe(true) - if (result.success) { - expect(result.data.type).toBe("text") + const result = decodeFormat({ type: "text" }) + expect(Exit.isSuccess(result)).toBe(true) + if (Exit.isSuccess(result)) { + expect(result.value.type).toBe("text") } }) test("parses json_schema format with defaults", () => { - const result = MessageV2.Format.zod.safeParse({ + const result = decodeFormat({ type: "json_schema", schema: { type: "object", properties: { name: { type: "string" } } }, }) - expect(result.success).toBe(true) - if (result.success) { - expect(result.data.type).toBe("json_schema") - if (result.data.type === "json_schema") { - expect(result.data.retryCount).toBe(2) // default value + expect(Exit.isSuccess(result)).toBe(true) + if (Exit.isSuccess(result)) { + expect(result.value.type).toBe("json_schema") + if (result.value.type === "json_schema") { + expect(result.value.retryCount).toBe(2) // default value } } }) test("parses json_schema format with custom retryCount", () => { - const result = MessageV2.Format.zod.safeParse({ + const result = decodeFormat({ type: "json_schema", schema: { type: "object" }, retryCount: 5, }) - expect(result.success).toBe(true) - if (result.success && result.data.type === "json_schema") { - expect(result.data.retryCount).toBe(5) + expect(Exit.isSuccess(result)).toBe(true) + if (Exit.isSuccess(result) && result.value.type === "json_schema") { + expect(result.value.retryCount).toBe(5) } }) test("rejects invalid type", () => { - const result = MessageV2.Format.zod.safeParse({ type: "invalid" }) - expect(result.success).toBe(false) + const result = decodeFormat({ type: "invalid" }) + expect(Exit.isFailure(result)).toBe(true) }) test("rejects json_schema without schema", () => { - const result = MessageV2.Format.zod.safeParse({ type: "json_schema" }) - expect(result.success).toBe(false) + const result = decodeFormat({ type: "json_schema" }) + expect(Exit.isFailure(result)).toBe(true) }) test("rejects negative retryCount", () => { - const result = MessageV2.Format.zod.safeParse({ + const result = decodeFormat({ type: "json_schema", schema: { type: "object" }, retryCount: -1, }) - expect(result.success).toBe(false) + expect(Exit.isFailure(result)).toBe(true) }) }) @@ -95,7 +100,7 @@ describe("structured-output.StructuredOutputError", () => { describe("structured-output.UserMessage", () => { test("user message accepts outputFormat", () => { - const result = MessageV2.User.zod.safeParse({ + const result = decodeUser({ id: MessageID.ascending(), sessionID: SessionID.descending(), role: "user", @@ -107,11 +112,11 @@ describe("structured-output.UserMessage", () => { schema: { type: "object" }, }, }) - expect(result.success).toBe(true) + expect(Exit.isSuccess(result)).toBe(true) }) test("user message works without outputFormat (optional)", () => { - const result = MessageV2.User.zod.safeParse({ + const result = decodeUser({ id: MessageID.ascending(), sessionID: SessionID.descending(), role: "user", @@ -119,7 +124,7 @@ describe("structured-output.UserMessage", () => { agent: "default", model: { providerID: "anthropic", modelID: "claude-3" }, }) - expect(result.success).toBe(true) + expect(Exit.isSuccess(result)).toBe(true) }) }) @@ -140,19 +145,19 @@ describe("structured-output.AssistantMessage", () => { } test("assistant message accepts structured", () => { - const result = MessageV2.Assistant.zod.safeParse({ + const result = decodeAssistant({ ...baseAssistantMessage, structured: { company: "Anthropic", founded: 2021 }, }) - expect(result.success).toBe(true) - if (result.success) { - expect(result.data.structured).toEqual({ company: "Anthropic", founded: 2021 }) + expect(Exit.isSuccess(result)).toBe(true) + if (Exit.isSuccess(result)) { + expect(result.value.structured).toEqual({ company: "Anthropic", founded: 2021 }) } }) test("assistant message works without structured_output (optional)", () => { - const result = MessageV2.Assistant.zod.safeParse(baseAssistantMessage) - expect(result.success).toBe(true) + const result = decodeAssistant(baseAssistantMessage) + expect(Exit.isSuccess(result)).toBe(true) }) }) From 42a0453945cf203d5c03c4f46bba92368de59dfd Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 16:12:31 -0400 Subject: [PATCH 040/378] Drop small session Zod statics (#26921) --- packages/opencode/src/session/revert.ts | 4 +--- packages/opencode/src/session/status.ts | 8 ++------ packages/opencode/src/session/todo.ts | 7 +------ packages/opencode/test/session/schema-decoding.test.ts | 7 ------- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index 12c81180e..ef9089c94 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -4,8 +4,6 @@ import { Snapshot } from "../snapshot" import { Storage } from "@/storage/storage" import { SyncEvent } from "../sync" import * as Log from "@opencode-ai/core/util/log" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" import * as Session from "./session" import { MessageV2 } from "./message-v2" import { SessionID, MessageID, PartID } from "./schema" @@ -18,7 +16,7 @@ export const RevertInput = Schema.Struct({ sessionID: SessionID, messageID: MessageID, partID: Schema.optional(PartID), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type RevertInput = Schema.Schema.Type export interface Interface { diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index 1dd36ec53..089559e2c 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -2,10 +2,8 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { InstanceState } from "@/effect/instance-state" import { SessionID } from "./schema" -import { zod } from "@opencode-ai/core/effect-zod" -import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema" +import { NonNegativeInt } from "@opencode-ai/core/schema" import { Effect, Layer, Context, Schema } from "effect" -import z from "zod" export const Info = Schema.Union([ Schema.Struct({ @@ -30,9 +28,7 @@ export const Info = Schema.Union([ Schema.Struct({ type: Schema.Literal("busy"), }), -]) - .annotate({ identifier: "SessionStatus" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +]).annotate({ identifier: "SessionStatus" }) export type Info = Schema.Schema.Type export const Event = { diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts index 9b7daf7f0..005b3b7c4 100644 --- a/packages/opencode/src/session/todo.ts +++ b/packages/opencode/src/session/todo.ts @@ -1,10 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { SessionID } from "./schema" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" import { Effect, Layer, Context, Schema } from "effect" -import z from "zod" import { Database } from "@/storage/db" import { eq } from "drizzle-orm" import { asc } from "drizzle-orm" @@ -16,9 +13,7 @@ export const Info = Schema.Struct({ description: "Current status of the task: pending, in_progress, completed, cancelled", }), priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }), -}) - .annotate({ identifier: "Todo" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Todo" }) export type Info = Schema.Schema.Type export const Event = { diff --git a/packages/opencode/test/session/schema-decoding.test.ts b/packages/opencode/test/session/schema-decoding.test.ts index a65137a2f..4422c5b71 100644 --- a/packages/opencode/test/session/schema-decoding.test.ts +++ b/packages/opencode/test/session/schema-decoding.test.ts @@ -221,14 +221,11 @@ describe("SessionRevert.RevertInput", () => { test("messageID is required, partID is optional", () => { const withPart = { sessionID, messageID, partID } expect(decode(withPart)).toEqual(withPart) - expect(SessionRevert.RevertInput.zod.parse(withPart)).toEqual(withPart) const noPart = { sessionID, messageID } expect(decode(noPart)).toEqual(noPart) - expect(SessionRevert.RevertInput.zod.parse(noPart)).toEqual(noPart) expect(() => decode({ sessionID })).toThrow() - expect(() => SessionRevert.RevertInput.zod.parse({ sessionID })).toThrow() }) }) @@ -247,7 +244,6 @@ describe("SessionStatus.Info", () => { test("idle / busy discriminators", () => { expect(decode({ type: "idle" })).toEqual({ type: "idle" }) expect(decode({ type: "busy" })).toEqual({ type: "busy" }) - expect(SessionStatus.Info.zod.parse({ type: "idle" })).toEqual({ type: "idle" }) }) test("retry carries attempt/message/action/next", () => { @@ -266,12 +262,10 @@ describe("SessionStatus.Info", () => { next: 500, } expect(decode(input)).toEqual(input) - expect(SessionStatus.Info.zod.parse(input)).toEqual(input) }) test("rejects unknown type", () => { expect(() => decode({ type: "bogus" })).toThrow() - expect(() => SessionStatus.Info.zod.parse({ type: "bogus" })).toThrow() }) }) @@ -281,7 +275,6 @@ describe("Todo.Info", () => { test("three-field round-trip", () => { const input = { content: "do a thing", status: "pending", priority: "high" } expect(decode(input)).toEqual(input) - expect(Todo.Info.zod.parse(input)).toEqual(input) }) }) From 9067218b74874bdffd3a53142c6b2d0ff65bb479 Mon Sep 17 00:00:00 2001 From: James Long Date: Mon, 11 May 2026 16:22:25 -0400 Subject: [PATCH 041/378] fix(core): always start worktrees as detached (#26931) --- packages/app/src/pages/layout.tsx | 2 +- .../src/control-plane/adapters/worktree.ts | 8 +-- packages/opencode/src/control-plane/types.ts | 6 +- packages/opencode/src/worktree/index.ts | 49 +++++++++------ .../opencode/test/project/worktree.test.ts | 63 ++++++++++++++++--- packages/sdk/js/src/v2/gen/types.gen.ts | 12 ++-- packages/sdk/openapi.json | 8 +-- 7 files changed, 101 insertions(+), 47 deletions(-) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index a08372649..45fcc6ee2 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1934,7 +1934,7 @@ export default function Layout(props: ParentProps) { if (!created?.directory) return - setWorkspaceName(created.directory, created.branch, project.id, created.branch) + setWorkspaceName(created.directory, created.branch ?? getFilename(created.directory), project.id, created.branch) const local = project.worktree const key = pathKey(created.directory) diff --git a/packages/opencode/src/control-plane/adapters/worktree.ts b/packages/opencode/src/control-plane/adapters/worktree.ts index 605d114ac..1c85d125a 100644 --- a/packages/opencode/src/control-plane/adapters/worktree.ts +++ b/packages/opencode/src/control-plane/adapters/worktree.ts @@ -22,11 +22,10 @@ export const WorktreeAdapter: WorkspaceAdapter = { description: "Create a git worktree", async configure(info) { const { AppRuntime, Worktree } = await loadWorktree() - const next = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.makeWorktreeInfo())) + const next = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.makeWorktreeInfo({ detached: true }))) return { ...info, name: next.name, - branch: next.branch, directory: next.directory, } }, @@ -38,7 +37,7 @@ export const WorktreeAdapter: WorkspaceAdapter = { svc.createFromInfo({ name: config.name, directory: config.directory, - branch: config.branch ?? config.name, + ...(config.branch ? { branch: config.branch } : {}), }), ), ) @@ -48,9 +47,8 @@ export const WorktreeAdapter: WorkspaceAdapter = { return (await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.list()))).map((info) => ({ type: "worktree", name: info.name, - branch: info.branch ?? null, + branch: info.branch, directory: info.directory, - extra: null, projectID: Instance.project.id, })) }, diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts index e78d728e0..e55ae2194 100644 --- a/packages/opencode/src/control-plane/types.ts +++ b/packages/opencode/src/control-plane/types.ts @@ -7,9 +7,9 @@ export const WorkspaceInfo = Schema.Struct({ id: WorkspaceID, type: Schema.String, name: Schema.String, - branch: Schema.NullOr(Schema.String), - directory: Schema.NullOr(Schema.String), - extra: Schema.NullOr(Schema.Unknown), + branch: Schema.optional(Schema.NullOr(Schema.String)), + directory: Schema.optional(Schema.NullOr(Schema.String)), + extra: Schema.optional(Schema.NullOr(Schema.Unknown)), projectID: ProjectID, }).annotate({ identifier: "Workspace" }) export type WorkspaceInfo = DeepMutable> diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index a6599debd..439f36e0a 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -29,7 +29,7 @@ export const Event = { "worktree.ready", Schema.Struct({ name: Schema.String, - branch: Schema.String, + branch: Schema.optional(Schema.String), }), ), Failed: BusEvent.define( @@ -42,7 +42,7 @@ export const Event = { export const Info = Schema.Struct({ name: Schema.String, - branch: Schema.String, + branch: Schema.optional(Schema.String), directory: Schema.String, }).annotate({ identifier: "Worktree" }) export type Info = Schema.Schema.Type @@ -143,7 +143,7 @@ function failedRemoves(...chunks: string[]) { // --------------------------------------------------------------------------- export interface Interface { - readonly makeWorktreeInfo: (name?: string) => Effect.Effect + readonly makeWorktreeInfo: (options?: { name?: string; detached?: boolean }) => Effect.Effect readonly createFromInfo: (info: Info, startCommand?: string) => Effect.Effect readonly create: (input?: CreateInput) => Effect.Effect readonly list: () => Effect.Effect<(Omit & { branch?: string })[]> @@ -194,25 +194,34 @@ export const layer: Layer.Layer< ) const MAX_NAME_ATTEMPTS = 26 - const candidate = Effect.fn("Worktree.candidate")(function* (root: string, base?: string) { + const candidate = Effect.fn("Worktree.candidate")(function* (input: { + root: string + name?: string + detached?: boolean + }) { const ctx = yield* InstanceState.context for (const attempt of Array.from({ length: MAX_NAME_ATTEMPTS }, (_, i) => i)) { - const name = base ? (attempt === 0 ? base : `${base}-${Slug.create()}`) : Slug.create() - const branch = `opencode/${name}` - const directory = pathSvc.join(root, name) + const name = input.name ? (attempt === 0 ? input.name : `${input.name}-${Slug.create()}`) : Slug.create() + const branch = input.detached ? undefined : `opencode/${name}` + const directory = pathSvc.join(input.root, name) if (yield* fs.exists(directory).pipe(Effect.orDie)) continue - const ref = `refs/heads/${branch}` - const branchCheck = yield* git(["show-ref", "--verify", "--quiet", ref], { cwd: ctx.worktree }) - if (branchCheck.code === 0) continue + if (branch) { + const ref = `refs/heads/${branch}` + const branchCheck = yield* git(["show-ref", "--verify", "--quiet", ref], { cwd: ctx.worktree }) + if (branchCheck.code === 0) continue + } - return { name, branch, directory } + return { name, directory, ...(branch ? { branch } : {}) } } throw new NameGenerationFailedError({ message: "Failed to generate a unique worktree name" }) }) - const makeWorktreeInfo = Effect.fn("Worktree.makeWorktreeInfo")(function* (name?: string) { + const makeWorktreeInfo = Effect.fn("Worktree.makeWorktreeInfo")(function* (input?: { + name?: string + detached?: boolean + }) { const ctx = yield* InstanceState.context if (ctx.project.vcs !== "git") { throw new NotGitError({ message: "Worktrees are only supported for git projects" }) @@ -221,15 +230,17 @@ export const layer: Layer.Layer< const root = pathSvc.join(Global.Path.data, "worktree", ctx.project.id) yield* fs.makeDirectory(root, { recursive: true }).pipe(Effect.orDie) - const base = name ? slugify(name) : "" - return yield* candidate(root, base || undefined) + return yield* candidate({ root, name: input?.name ? slugify(input.name) : "", detached: input?.detached }) }) const setup = Effect.fnUntraced(function* (info: Info) { const ctx = yield* InstanceState.context - const created = yield* git(["worktree", "add", "--no-checkout", "-b", info.branch, info.directory], { - cwd: ctx.worktree, - }) + const created = yield* git( + info.branch + ? ["worktree", "add", "--no-checkout", "-b", info.branch, info.directory] + : ["worktree", "add", "--no-checkout", "--detach", info.directory, "HEAD"], + { cwd: ctx.worktree }, + ) if (created.code !== 0) { throw new CreateFailedError({ message: created.stderr || created.text || "Failed to create git worktree" }) } @@ -280,7 +291,7 @@ export const layer: Layer.Layer< workspace: workspaceID, payload: { type: Event.Ready.type, - properties: { name: info.name, branch: info.branch }, + properties: { name: info.name, ...(info.branch ? { branch: info.branch } : {}) }, }, }) @@ -296,7 +307,7 @@ export const layer: Layer.Layer< }) const create = Effect.fn("Worktree.create")(function* (input?: CreateInput) { - const info = yield* makeWorktreeInfo(input?.name) + const info = yield* makeWorktreeInfo({ name: input?.name }) yield* createFromInfo(info, input?.startCommand) return info }) diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index 4f0ead54e..b1b9d22b7 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -21,13 +21,13 @@ function normalize(input: string) { async function waitReady() { const { GlobalBus } = await import("../../src/bus/global") - return await new Promise<{ name: string; branch: string }>((resolve, reject) => { + return await new Promise<{ name: string; branch?: string }>((resolve, reject) => { const timer = setTimeout(() => { GlobalBus.off("event", on) reject(new Error("timed out waiting for worktree.ready")) }, 10_000) - function on(evt: { directory?: string; payload: { type: string; properties: { name: string; branch: string } } }) { + function on(evt: { directory?: string; payload: { type: string; properties: { name: string; branch?: string } } }) { if (evt.payload.type !== Worktree.Event.Ready.type) return clearTimeout(timer) GlobalBus.off("event", on) @@ -63,7 +63,7 @@ describe("Worktree", () => { () => Effect.gen(function* () { const svc = yield* Worktree.Service - const info = yield* svc.makeWorktreeInfo("my-feature") + const info = yield* svc.makeWorktreeInfo({ name: "my-feature" }) expect(info.name).toBe("my-feature") expect(info.branch).toBe("opencode/my-feature") @@ -77,7 +77,7 @@ describe("Worktree", () => { () => Effect.gen(function* () { const svc = yield* Worktree.Service - const info = yield* svc.makeWorktreeInfo("My Feature Branch!") + const info = yield* svc.makeWorktreeInfo({ name: "My Feature Branch!" }) expect(info.name).toBe("my-feature-branch") }), @@ -85,6 +85,22 @@ describe("Worktree", () => { ), ) + it.live("omits branch for detached info", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const svc = yield* Worktree.Service + yield* Effect.promise(() => $`git branch opencode/my-feature`.cwd(dir).quiet()) + + const info = yield* svc.makeWorktreeInfo({ name: "my-feature", detached: true }) + + expect(info.name).toBe("my-feature") + expect(info.branch).toBeUndefined() + }), + { git: true }, + ), + ) + it.live("throws NotGitError for non-git directories", () => provideTmpdirInstance(() => Effect.gen(function* () { @@ -96,6 +112,35 @@ describe("Worktree", () => { }), ), ) + + wintest("creates detached git worktree when info has no branch", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const svc = yield* Worktree.Service + const info = yield* svc.makeWorktreeInfo({ name: "detached-test", detached: true }) + const ready = waitReady() + yield* svc.createFromInfo(info) + + const list = yield* Effect.promise(() => $`git worktree list --porcelain`.cwd(dir).quiet().text()) + const normalizedList = normalize(list) + const normalizedDir = normalize(info.directory) + expect(normalizedList).toContain(normalizedDir) + + const branch = yield* Effect.promise(() => + $`git symbolic-ref -q --short HEAD`.cwd(info.directory).quiet().nothrow(), + ) + expect(branch.exitCode).not.toBe(0) + + const props = yield* Effect.promise(() => ready) + expect(props.name).toBe(info.name) + expect(props.branch).toBeUndefined() + + yield* svc.remove({ directory: info.directory }) + }), + { git: true }, + ), + ) }) describe("create + remove lifecycle", () => { @@ -107,7 +152,7 @@ describe("Worktree", () => { const info = yield* svc.create() expect(info.name).toBeDefined() - expect(info.branch).toStartWith("opencode/") + expect(info.branch ?? "").toStartWith("opencode/") expect(info.directory).toBeDefined() yield* Effect.promise(() => Bun.sleep(1000)) @@ -128,7 +173,7 @@ describe("Worktree", () => { const info = yield* svc.create() expect(info.name).toBeDefined() - expect(info.branch).toStartWith("opencode/") + expect(info.branch ?? "").toStartWith("opencode/") const text = yield* Effect.promise(() => $`git worktree list --porcelain`.cwd(dir).quiet().text()) const next = yield* Effect.promise(() => fs.realpath(info.directory).catch(() => info.directory)) @@ -183,7 +228,7 @@ describe("Worktree", () => { (dir) => Effect.gen(function* () { const svc = yield* Worktree.Service - const info = yield* svc.makeWorktreeInfo("from-info-test") + const info = yield* svc.makeWorktreeInfo({ name: "from-info-test" }) const ready = waitReady() yield* svc.createFromInfo(info) @@ -216,10 +261,10 @@ describe("Worktree", () => { const list = yield* svc.list() const directory = yield* Effect.promise(() => fs.realpath(target).catch(() => target)) - expect(list).toContainEqual({ + expect(list.map((item) => ({ ...item, directory: normalize(item.directory) }))).toContainEqual({ name: path.basename(parent), branch, - directory: directory.toLowerCase(), + directory: normalize(directory), }) yield* svc.remove({ directory: target }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index da80645ad..c7a479f5a 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1398,7 +1398,7 @@ export type WorktreeCreateInput = { export type Worktree = { name: string - branch: string + branch?: string directory: string } @@ -1795,9 +1795,9 @@ export type Workspace = { id: string type: string name: string - branch: string | null - directory: string | null - extra: unknown | null + branch?: string | null + directory?: string | null + extra?: unknown | null projectID: string timeUsed: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" } @@ -2566,7 +2566,7 @@ export type EventWorktreeReady = { type: "worktree.ready" properties: { name: string - branch: string + branch?: string } } @@ -6772,7 +6772,7 @@ export type ExperimentalWorkspaceCreateData = { body?: { id?: string type: string - branch: string | null + branch?: string | null extra?: unknown | null } path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index df0427f45..3d452cc9c 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -8540,7 +8540,7 @@ ] } }, - "required": ["type", "branch"], + "required": ["type"], "additionalProperties": false } } @@ -12803,7 +12803,7 @@ "type": "string" } }, - "required": ["name", "branch", "directory"], + "required": ["name", "directory"], "additionalProperties": false }, "WorktreeRemoveInput": { @@ -14027,7 +14027,7 @@ ] } }, - "required": ["id", "type", "name", "branch", "directory", "extra", "projectID", "timeUsed"], + "required": ["id", "type", "name", "projectID", "timeUsed"], "additionalProperties": false }, "WorkspaceWarpError": { @@ -16580,7 +16580,7 @@ "type": "string" } }, - "required": ["name", "branch"], + "required": ["name"], "additionalProperties": false } }, From cc95197d723fa9e1c5db0990ebfbe8ab846ef8d8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 16:49:08 -0400 Subject: [PATCH 042/378] Drop prompt input Zod statics (#26923) --- packages/opencode/src/session/prompt.ts | 12 +++---- packages/opencode/src/session/session.ts | 31 +++++++------------ .../test/server/global-session-list.test.ts | 4 +-- .../test/session/schema-decoding.test.ts | 21 ------------- 4 files changed, 16 insertions(+), 52 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 4950be084..2de4bbd30 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -46,8 +46,6 @@ import { Truncate } from "@/tool/truncate" import { decodeDataUrl } from "@/util/data-url" import { Process } from "@/util/process" import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" import * as EffectLogger from "@opencode-ai/core/effect/logger" import { InstanceState } from "@/effect/instance-state" import { TaskTool, type TaskPromptOps } from "@/tool/task" @@ -2054,14 +2052,12 @@ export const PromptInput = Schema.Struct({ MessageV2.SubtaskPartInput, ]).annotate({ discriminator: "type" }), ), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type PromptInput = Schema.Schema.Type export class LoopInput extends Schema.Class("SessionPrompt.LoopInput")({ sessionID: SessionID, -}) { - static readonly zod = zod(this) -} +}) {} export const ShellInput = Schema.Struct({ sessionID: SessionID, @@ -2069,7 +2065,7 @@ export const ShellInput = Schema.Struct({ agent: Schema.String, model: Schema.optional(ModelRef), command: Schema.String, -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type ShellInput = Schema.Schema.Type export const CommandInput = Schema.Struct({ @@ -2097,7 +2093,7 @@ export const CommandInput = Schema.Struct({ ]).annotate({ discriminator: "type" }), ), ), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type CommandInput = Schema.Schema.Type /** @internal Exported for testing */ diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index f50f8750b..92b4329e6 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -37,8 +37,7 @@ import type { Provider } from "@/provider/provider" import { Permission } from "@/permission" import { Global } from "@opencode-ai/core/global" import { Effect, Layer, Option, Context, Schema, Types } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema" +import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema" const log = Log.create({ service: "session" }) @@ -193,26 +192,20 @@ export const Info = Schema.Struct({ time: Time, permission: optionalOmitUndefined(Permission.Ruleset), revert: optionalOmitUndefined(Revert), -}) - .annotate({ identifier: "Session" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Session" }) export type Info = Types.DeepMutable> export const ProjectInfo = Schema.Struct({ id: ProjectID, name: optionalOmitUndefined(Schema.String), worktree: Schema.String, -}) - .annotate({ identifier: "ProjectSummary" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "ProjectSummary" }) export type ProjectInfo = Types.DeepMutable> export const GlobalInfo = Schema.Struct({ ...Info.fields, project: Schema.NullOr(ProjectInfo), -}) - .annotate({ identifier: "GlobalSession" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "GlobalSession" }) export type GlobalInfo = Types.DeepMutable> export const CreateInput = Schema.optional( @@ -224,36 +217,34 @@ export const CreateInput = Schema.optional( permission: Schema.optional(Permission.Ruleset), workspaceID: Schema.optional(WorkspaceID), }), -).pipe(withStatics((s) => ({ zod: zod(s) }))) +) export type CreateInput = Types.DeepMutable> export const ForkInput = Schema.Struct({ sessionID: SessionID, messageID: Schema.optional(MessageID), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export const GetInput = SessionID export const ChildrenInput = SessionID export const RemoveInput = SessionID -export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema.String }).pipe( - withStatics((s) => ({ zod: zod(s) })), -) +export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema.String }) export const SetArchivedInput = Schema.Struct({ sessionID: SessionID, time: Schema.optional(ArchivedTimestamp), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export const SetPermissionInput = Schema.Struct({ sessionID: SessionID, permission: Permission.Ruleset, -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export const SetRevertInput = Schema.Struct({ sessionID: SessionID, revert: Schema.optional(Revert), summary: Schema.optional(Summary), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export const MessagesInput = Schema.Struct({ sessionID: SessionID, limit: Schema.optional(NonNegativeInt), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type ListInput = { directory?: string scope?: "project" diff --git a/packages/opencode/test/server/global-session-list.test.ts b/packages/opencode/test/server/global-session-list.test.ts index 936808951..04348e5c0 100644 --- a/packages/opencode/test/server/global-session-list.test.ts +++ b/packages/opencode/test/server/global-session-list.test.ts @@ -1,7 +1,5 @@ import { describe, expect, test } from "bun:test" import { Effect } from "effect" -import z from "zod" -import { Instance } from "../../src/project/instance" import { WithInstance } from "../../src/project/with-instance" import { Project } from "@/project/project" import { Session as SessionNs } from "@/session/session" @@ -19,7 +17,7 @@ const svc = { create(input?: SessionNs.CreateInput) { return run(SessionNs.Service.use((svc) => svc.create(input))) }, - setArchived(input: z.output) { + setArchived(input: typeof SessionNs.SetArchivedInput.Type) { return run(SessionNs.Service.use((svc) => svc.setArchived(input))) }, } diff --git a/packages/opencode/test/session/schema-decoding.test.ts b/packages/opencode/test/session/schema-decoding.test.ts index 4422c5b71..c0201a475 100644 --- a/packages/opencode/test/session/schema-decoding.test.ts +++ b/packages/opencode/test/session/schema-decoding.test.ts @@ -49,7 +49,6 @@ describe("Session.Info", () => { time: { created: 1, updated: 2 }, } expect(decode(input)).toEqual(input) - expect(Session.Info.zod.parse(input)).toEqual(input) }) test("round-trips every optional field", () => { @@ -80,7 +79,6 @@ describe("Session.Info", () => { }, } expect(decode(input)).toEqual(input) - expect(Session.Info.zod.parse(input)).toEqual(input) }) test("accepts migrated summary diffs without file details", () => { @@ -100,19 +98,16 @@ describe("Session.Info", () => { time: { created: 1, updated: 2 }, } expect(decode(input)).toEqual(input) - expect(Session.Info.zod.parse(input)).toEqual(input) }) test("rejects unbranded session id", () => { const bad = { id: "not-a-session-id" } as unknown expect(() => decode(bad)).toThrow() - expect(() => Session.Info.zod.parse(bad)).toThrow() }) test("rejects missing required fields", () => { const bad = { id: sessionID } as unknown expect(() => decode(bad)).toThrow() - expect(() => Session.Info.zod.parse(bad)).toThrow() }) }) @@ -124,8 +119,6 @@ describe("Session.ProjectInfo", () => { const withName = { ...noName, name: "alpha" } expect(decode(noName)).toEqual(noName) expect(decode(withName)).toEqual(withName) - expect(Session.ProjectInfo.zod.parse(noName)).toEqual(noName) - expect(Session.ProjectInfo.zod.parse(withName)).toEqual(withName) }) }) @@ -144,7 +137,6 @@ describe("Session.GlobalInfo", () => { project: null, } expect(decode(input)).toEqual(input) - expect(Session.GlobalInfo.zod.parse(input)).toEqual(input) }) test("accepts populated project", () => { @@ -159,7 +151,6 @@ describe("Session.GlobalInfo", () => { project: { id: projectID, worktree: "/tmp/wt", name: "alpha" }, } expect(decode(input)).toEqual(input) - expect(Session.GlobalInfo.zod.parse(input)).toEqual(input) }) }) @@ -167,7 +158,6 @@ describe("Session input schemas", () => { test("CreateInput accepts undefined and populated forms", () => { const decode = decodeUnknown(Session.CreateInput) expect(decode(undefined)).toBeUndefined() - expect(Session.CreateInput.zod.parse(undefined)).toBeUndefined() const populated = { parentID: sessionID, @@ -176,23 +166,19 @@ describe("Session input schemas", () => { workspaceID, } expect(decode(populated)).toEqual(populated) - expect(Session.CreateInput.zod.parse(populated)).toEqual(populated) }) test("ForkInput round-trips", () => { const decode = decodeUnknown(Session.ForkInput) const input = { sessionID, messageID } expect(decode(input)).toEqual(input) - expect(Session.ForkInput.zod.parse(input)).toEqual(input) // messageID is optional const bare = { sessionID } expect(decode(bare)).toEqual(bare) - expect(Session.ForkInput.zod.parse(bare)).toEqual(bare) }) test("SetTitleInput rejects missing title", () => { expect(() => decodeUnknown(Session.SetTitleInput)({ sessionID })).toThrow() - expect(() => Session.SetTitleInput.zod.parse({ sessionID })).toThrow() }) test("SetArchivedInput accepts both with and without time", () => { @@ -282,7 +268,6 @@ describe("SessionPrompt input schemas", () => { test("LoopInput is just sessionID", () => { const decode = decodeUnknown(SessionPrompt.LoopInput) expect(decode({ sessionID })).toEqual({ sessionID }) - expect(SessionPrompt.LoopInput.zod.parse({ sessionID } as unknown)).toEqual({ sessionID }) }) test("ShellInput requires agent + command", () => { @@ -290,7 +275,6 @@ describe("SessionPrompt input schemas", () => { const expected = { sessionID, agent: "build", command: "echo hi" } const input: unknown = expected expect(decode(input)).toEqual(expected) - expect(SessionPrompt.ShellInput.zod.parse(input as unknown)).toEqual(expected) expect(() => decode({ sessionID })).toThrow() }) @@ -308,9 +292,6 @@ describe("SessionPrompt input schemas", () => { expect(decoded.parts).toHaveLength(2) expect(decoded.parts[0]).toMatchObject({ type: "text", text: "hello" }) expect(decoded.parts[1]).toMatchObject({ type: "file", mime: "image/png" }) - - const viaZod = SessionPrompt.PromptInput.zod.parse(input) - expect(viaZod.parts).toHaveLength(2) }) test("PromptInput rejects unknown part type", () => { @@ -320,7 +301,6 @@ describe("SessionPrompt input schemas", () => { parts: [{ type: "nonsense", payload: 42 }], } expect(() => decode(bad)).toThrow() - expect(() => SessionPrompt.PromptInput.zod.parse(bad)).toThrow() }) test("CommandInput round-trips core fields", () => { @@ -332,6 +312,5 @@ describe("SessionPrompt input schemas", () => { } const input: unknown = expected expect(decode(input)).toEqual(expected) - expect(SessionPrompt.CommandInput.zod.parse(input)).toEqual(expected) }) }) From fe7ca3421e22a0ac89718211d9932417417e398d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 16:49:22 -0400 Subject: [PATCH 043/378] Drop Config Info Zod static (#26933) --- packages/opencode/script/schema.ts | 3 ++- packages/opencode/src/config/config.ts | 23 ++----------------- packages/opencode/test/config/config.test.ts | 2 +- .../opencode/test/session/compaction.test.ts | 4 ++-- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/packages/opencode/script/schema.ts b/packages/opencode/script/schema.ts index b1a587075..eaa678524 100755 --- a/packages/opencode/script/schema.ts +++ b/packages/opencode/script/schema.ts @@ -2,6 +2,7 @@ import { z } from "zod" import { Config } from "@/config/config" +import { zodObject } from "@opencode-ai/core/effect-zod" import { TuiConfig } from "../src/cli/cmd/tui/config/tui" function generate(schema: z.ZodType) { @@ -55,7 +56,7 @@ const configFile = process.argv[2] const tuiFile = process.argv[3] console.log(configFile) -await Bun.write(configFile, JSON.stringify(generate(Config.Info.zod), null, 2)) +await Bun.write(configFile, JSON.stringify(generate(zodObject(Config.Info).strict().meta({ ref: "Config" })), null, 2)) if (tuiFile) { console.log(tuiFile) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index c05d562c9..2e282c232 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -22,8 +22,7 @@ import { InstanceState } from "@/effect/instance-state" import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { containsPath } from "../project/instance-context" -import { zod } from "@opencode-ai/core/effect-zod" -import { NonNegativeInt, PositiveInt, withStatics, type DeepMutable } from "@opencode-ai/core/schema" +import { NonNegativeInt, PositiveInt, type DeepMutable } from "@opencode-ai/core/schema" import { ConfigAgent } from "./agent" import { ConfigAttachment } from "./attachment" import { ConfigCommand } from "./command" @@ -112,8 +111,6 @@ async function resolveLoadedPlugins( return config } -export const Server = ConfigServer.Server.zod -export const Layout = ConfigLayout.Layout.zod export type Layout = ConfigLayout.Layout const LogLevelRef = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({ @@ -121,14 +118,6 @@ const LogLevelRef = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate description: "Log level", }) -// The Effect Schema is the canonical source of truth. The `.zod` compatibility -// surface is derived from it so plugin/SDK Zod consumers keep working without -// a parallel hand-maintained Zod definition. -// -// The walker emits `z.object({...})` which is non-strict by default. Config -// historically uses `.strict()` (additionalProperties: false in openapi.json), -// so layer that on after derivation. Re-apply the Config ref afterward -// since `.strict()` strips the walker's meta annotation. export const Info = Schema.Struct({ $schema: Schema.optional(Schema.String).annotate({ description: "JSON schema reference for configuration validation", @@ -301,15 +290,7 @@ export const Info = Schema.Struct({ }), }), ), -}) - .annotate({ identifier: "Config" }) - .pipe( - withStatics((s) => ({ - zod: (zod(s) as unknown as z.ZodObject).strict().meta({ ref: "Config" }) as unknown as z.ZodType< - DeepMutable> - >, - })), - ) +}).annotate({ identifier: "Config" }) // Uses the shared `DeepMutable` from `@opencode-ai/core/schema`. See the definition // there for why the local variant is needed over `Types.DeepMutable` from diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index bbe585237..5f49a3f3a 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -251,7 +251,7 @@ test("updates global config and omits empty shell key in jsonc", async () => { const file = path.join(tmp.path, "opencode.jsonc") const writtenConfig = await Filesystem.readText(file) - const parsed = ConfigParse.schema(Config.Info.zod, ConfigParse.jsonc(writtenConfig, file), file) + const parsed = ConfigParse.effectSchema(Config.Info, ConfigParse.jsonc(writtenConfig, file), file) expect(writtenConfig).not.toContain('"shell"') expect(parsed.shell).toBeUndefined() expect(parsed.model).toBe("test/model") diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 13400d79c..c7f349d5c 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, mock, test } from "bun:test" import { APICallError } from "ai" -import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect" +import { Cause, Deferred, Effect, Exit, Fiber, Layer, Schema } from "effect" import * as Stream from "effect/Stream" import { Bus } from "../../src/bus" import { Config } from "@/config/config" @@ -211,7 +211,7 @@ function layer(result: "continue" | "compact") { } function cfg(compaction?: Config.Info["compaction"]) { - const base = Config.Info.zod.parse({}) + const base = Schema.decodeUnknownSync(Config.Info)({}) as Config.Info return TestConfig.layer({ get: () => Effect.succeed({ ...base, compaction }), }) From d3caac527061f427d25f30d1359912c6ca07b1b7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 17:09:57 -0400 Subject: [PATCH 044/378] chore(deps): upgrade effect to 4.0.0-beta.65 (#26934) --- bun.lock | 14 +++++++------- package.json | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bun.lock b/bun.lock index 37df44b0b..4268e5fb7 100644 --- a/bun.lock +++ b/bun.lock @@ -684,8 +684,8 @@ }, "catalog": { "@cloudflare/workers-types": "4.20251008.0", - "@effect/opentelemetry": "4.0.0-beta.57", - "@effect/platform-node": "4.0.0-beta.57", + "@effect/opentelemetry": "4.0.0-beta.65", + "@effect/platform-node": "4.0.0-beta.65", "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", "@lydell/node-pty": "1.2.0-beta.10", @@ -718,7 +718,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.59", + "effect": "4.0.0-beta.65", "fuzzysort": "3.1.0", "hono": "4.10.7", "hono-openapi": "1.1.2", @@ -1079,11 +1079,11 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="], - "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.57", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.57" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-gdjZPEP0QQg4qmI1vd+443kheeQZKytrjJIzCJncy6ZEpyk/SfrqeStLqLXdTRcms3IB0ls0vOV7KNq7YmBRVA=="], + "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.65", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.65" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-0CD2fSsXrDM7FP2WFkbGJO1DwMqWR3UKHh6oBDXPHAPA+RsJSKoh3pLQsbQfldLuKnhOy87Bv0v9r9IdrIHCQw=="], - "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.57", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.57", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.57", "ioredis": "^5.7.0" } }, "sha512-la0xxPSAYOsY0d+uVxEBxok3jYB31iPQmIaZZRUj2SNWqcGGHJc6KorKtI8guqSLuv9FGZ255kBWXRbG6hMeeg=="], + "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.65", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.65", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.65", "ioredis": "^5.7.0" } }, "sha512-QQy3KRcMwP0TngQdfQGl2u1zp03B7k7DuF5SNS8aZhD0dDBpKZpCwFad1ODY5qdY3ycPgMwBwKRRK7y/aw0C9w=="], - "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.57", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.57" } }, "sha512-C976X6f+qHUtLSqcqImuCrjhAHnJV17NC2RvvybsAuDfkyIWU4MyiO2XwgiBeijeNupyr1M/KPKnyjtkNxV9Hw=="], + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.65", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.65" } }, "sha512-3rY8F3WLEax6Hj08GI/OvDIH+KqjfxH7RM2bAMfgR75NgRmwDtny1P49PtPkoRjH5dcdtThThtsvE4X9OTZkpQ=="], "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], @@ -3037,7 +3037,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "effect": ["effect@4.0.0-beta.59", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA=="], + "effect": ["effect@4.0.0-beta.65", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-QYKvQPAj3CmtsvWkHQww15wX4KG2gNsszDWEcOO5sZCMknp66u6Si/Opmt3wwWCwsyvRmDAdIg+JIz5qzbbFIw=="], "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], diff --git a/package.json b/package.json index 5faf8be92..6d82864d6 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "packages/slack" ], "catalog": { - "@effect/opentelemetry": "4.0.0-beta.57", - "@effect/platform-node": "4.0.0-beta.57", + "@effect/opentelemetry": "4.0.0-beta.65", + "@effect/platform-node": "4.0.0-beta.65", "@npmcli/arborist": "9.4.0", "@types/bun": "1.3.12", "@types/cross-spawn": "6.0.6", @@ -55,7 +55,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.59", + "effect": "4.0.0-beta.65", "ai": "6.0.168", "cross-spawn": "7.0.6", "hono": "4.10.7", From fd65d29dcc3e09a6b9314890ae1fc5e9cfaef61d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 17:14:18 -0400 Subject: [PATCH 045/378] Drop unused opencode Zod statics (#26935) --- packages/opencode/src/auth/index.ts | 6 +- .../opencode/src/cli/cmd/tui/config/tui.ts | 7 +- packages/opencode/src/command/index.ts | 7 +- packages/opencode/src/config/agent.ts | 9 +- packages/opencode/src/config/attachment.ts | 11 +- packages/opencode/src/config/config.ts | 6 +- packages/opencode/src/config/console-state.ts | 5 +- packages/opencode/src/config/formatter.ts | 8 +- packages/opencode/src/config/layout.ts | 6 +- packages/opencode/src/config/mcp.ts | 19 +-- packages/opencode/src/config/model-id.ts | 5 +- packages/opencode/src/config/parse.ts | 16 +-- packages/opencode/src/config/permission.ts | 22 +--- packages/opencode/src/config/plugin.ts | 2 +- packages/opencode/src/config/provider.ts | 9 +- packages/opencode/src/config/reference.ts | 6 +- packages/opencode/src/config/server.ts | 7 +- packages/opencode/src/config/skills.ts | 4 +- packages/opencode/src/lsp/lsp.ts | 20 +-- packages/opencode/src/mcp/index.ts | 10 +- packages/opencode/src/permission/index.ts | 36 ++--- packages/opencode/src/project/project.ts | 11 +- packages/opencode/src/project/vcs.ts | 20 +-- packages/opencode/src/provider/auth.ts | 17 +-- packages/opencode/src/provider/provider.ts | 15 +-- packages/opencode/src/provider/schema.ts | 8 +- packages/opencode/src/question/index.ts | 30 +---- packages/opencode/src/session/message-v2.ts | 123 +++++------------- packages/opencode/src/session/message.ts | 54 +++----- packages/opencode/src/session/summary.ts | 4 +- packages/opencode/src/sync/README.md | 20 +-- packages/opencode/src/tool/schema.ts | 2 - packages/opencode/test/config/config.test.ts | 18 +-- .../test/session/schema-decoding.test.ts | 7 +- 34 files changed, 161 insertions(+), 389 deletions(-) diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index f7c631935..9d30ea142 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -1,6 +1,5 @@ import path from "path" import { Effect, Layer, Record, Result, Schema, Context } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" import { NonNegativeInt } from "@opencode-ai/core/schema" import { Global } from "@opencode-ai/core/global" import { AppFileSystem } from "@opencode-ai/core/filesystem" @@ -32,9 +31,8 @@ export class WellKnown extends Schema.Class("WellKnownAuth")({ token: Schema.String, }) {} -const _Info = Schema.Union([Oauth, Api, WellKnown]).annotate({ discriminator: "type", identifier: "Auth" }) -export const Info = Object.assign(_Info, { zod: zod(_Info) }) -export type Info = Schema.Schema.Type +export const Info = Schema.Union([Oauth, Api, WellKnown]).annotate({ discriminator: "type", identifier: "Auth" }) +export type Info = Schema.Schema.Type export class AuthError extends Schema.TaggedErrorClass()("AuthError", { message: Schema.String, diff --git a/packages/opencode/src/cli/cmd/tui/config/tui.ts b/packages/opencode/src/cli/cmd/tui/config/tui.ts index 14d991816..d7409cd2d 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui.ts @@ -5,6 +5,7 @@ import { createBindingLookup } from "@opentui/keymap/extras" import { mergeDeep, unique } from "remeda" import { Context, Effect, Fiber, Layer } from "effect" import { ConfigParse } from "@/config/parse" +import { InvalidError } from "@/config/error" import * as ConfigPaths from "@/config/paths" import { migrateTuiConfig } from "./tui-migrate" import { KeymapLeaderTimeoutDefault, TuiInfo, TuiJsonSchemaInfo } from "./tui-schema" @@ -91,10 +92,12 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: if (!isRecord(data)) return {} as Info // Flatten a nested "tui" key so users who wrote `{ "tui": { ... } }` inside tui.json // (mirroring the old opencode.json shape) still get their settings applied. - const validated = ConfigParse.schema(Info, normalize(data), configFilepath) + const parsed = Info.safeParse(normalize(data)) + if (!parsed.success) throw new InvalidError({ path: configFilepath, issues: parsed.error.issues }) + const validated = parsed.data return yield* resolvePlugins(validated, configFilepath) }).pipe( - // catchCause (not tapErrorCause + orElseSucceed) because ConfigParse.jsonc/.schema + // catchCause (not tapErrorCause + orElseSucceed) because JSONC parsing and validation // can sync-throw — those become defects, which orElseSucceed wouldn't catch. Effect.catchCause((cause) => Effect.sync(() => { diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index e26c4068b..54cfe4fcc 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -5,8 +5,7 @@ import type { InstanceContext } from "@/project/instance" import { SessionID, MessageID } from "@/session/schema" import { Effect, Layer, Context, Schema } from "effect" import z from "zod" -import { zod, ZodOverride } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" +import { ZodOverride } from "@opencode-ai/core/effect-zod" import { Config } from "@/config/config" import { MCP } from "../mcp" import { Skill } from "../skill" @@ -39,9 +38,7 @@ export const Info = Schema.Struct({ template: Schema.Unknown.annotate({ [ZodOverride]: z.promise(z.string()).or(z.string()) }), subtask: Schema.optional(Schema.Boolean), hints: Schema.Array(Schema.String), -}) - .annotate({ identifier: "Command" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Command" }) // for some reason zod is inferring `string` for z.promise(z.string()).or(z.string()) so we have to manually override it export type Info = Omit, "template"> & { template: Promise | string } diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 94c8d8fe0..a6719e867 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -2,8 +2,7 @@ export * as ConfigAgent from "./agent" import { Exit, Schema, SchemaGetter } from "effect" import { Bus } from "@/bus" -import { zod } from "@opencode-ai/core/effect-zod" -import { PositiveInt, withStatics } from "@opencode-ai/core/schema" +import { PositiveInt } from "@opencode-ai/core/schema" import * as Log from "@opencode-ai/core/util/log" import { NamedError } from "@opencode-ai/core/util/error" import { Glob } from "@opencode-ai/core/util/glob" @@ -102,9 +101,7 @@ export const Info = AgentSchema.pipe( decode: SchemaGetter.transform(normalize), encode: SchemaGetter.passthrough({ strict: false }), }), -) - .annotate({ identifier: "AgentConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +).annotate({ identifier: "AgentConfig" }) export type Info = Schema.Schema.Type export async function load(dir: string) { @@ -134,7 +131,7 @@ export async function load(dir: string) { ...md.data, prompt: md.content.trim(), } - result[config.name] = ConfigParse.effectSchema(Info, config, item) + result[config.name] = ConfigParse.schema(Info, config, item) } return result } diff --git a/packages/opencode/src/config/attachment.ts b/packages/opencode/src/config/attachment.ts index 7af429afd..a5fc59973 100644 --- a/packages/opencode/src/config/attachment.ts +++ b/packages/opencode/src/config/attachment.ts @@ -1,8 +1,7 @@ export * as ConfigAttachment from "./attachment" import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { PositiveInt, withStatics } from "@opencode-ai/core/schema" +import { PositiveInt } from "@opencode-ai/core/schema" export const Image = Schema.Struct({ auto_resize: Schema.optional(Schema.Boolean).annotate({ @@ -17,14 +16,10 @@ export const Image = Schema.Struct({ max_base64_bytes: Schema.optional(PositiveInt).annotate({ description: "Maximum base64 payload bytes for an image attachment (default: 4718592)", }), -}) - .annotate({ identifier: "ImageAttachmentConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "ImageAttachmentConfig" }) export type Image = Schema.Schema.Type export const Info = Schema.Struct({ image: Schema.optional(Image).annotate({ description: "Image attachment configuration" }), -}) - .annotate({ identifier: "AttachmentConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "AttachmentConfig" }) export type Info = Schema.Schema.Type diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 2e282c232..4b10665ac 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -388,7 +388,7 @@ export const layer = Layer.effect( ), ) const parsed = ConfigParse.jsonc(expanded, source) - const data = ConfigParse.effectSchema(Info, normalizeLoadedConfig(parsed, source), source) + const data = ConfigParse.schema(Info, normalizeLoadedConfig(parsed, source), source) if (!("path" in options)) return data yield* Effect.promise(() => resolveLoadedPlugins(data, options.path)) @@ -786,7 +786,7 @@ export const layer = Layer.effect( let next: Info let changed: boolean if (!file.endsWith(".jsonc")) { - const existing = ConfigParse.effectSchema(Info, ConfigParse.jsonc(before, file), file) + const existing = ConfigParse.schema(Info, ConfigParse.jsonc(before, file), file) const merged = mergeDeep(writable(existing), patch) const serialized = JSON.stringify(merged, null, 2) changed = serialized !== before @@ -794,7 +794,7 @@ export const layer = Layer.effect( next = merged } else { const updated = patchJsonc(before, patch) - next = ConfigParse.effectSchema(Info, ConfigParse.jsonc(updated, file), file) + next = ConfigParse.schema(Info, ConfigParse.jsonc(updated, file), file) changed = updated !== before if (changed) yield* fs.writeFileString(file, updated).pipe(Effect.orDie) } diff --git a/packages/opencode/src/config/console-state.ts b/packages/opencode/src/config/console-state.ts index 485e33416..d52a14840 100644 --- a/packages/opencode/src/config/console-state.ts +++ b/packages/opencode/src/config/console-state.ts @@ -1,14 +1,11 @@ import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" import { NonNegativeInt } from "@opencode-ai/core/schema" export class ConsoleState extends Schema.Class("ConsoleState")({ consoleManagedProviders: Schema.mutable(Schema.Array(Schema.String)), activeOrgName: Schema.optional(Schema.String), switchableOrgCount: NonNegativeInt, -}) { - static readonly zod = zod(this) -} +}) {} export const emptyConsoleState: ConsoleState = ConsoleState.make({ consoleManagedProviders: [], diff --git a/packages/opencode/src/config/formatter.ts b/packages/opencode/src/config/formatter.ts index 222a75005..7539fe4a7 100644 --- a/packages/opencode/src/config/formatter.ts +++ b/packages/opencode/src/config/formatter.ts @@ -1,17 +1,13 @@ export * as ConfigFormatter from "./formatter" import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" export const Entry = Schema.Struct({ disabled: Schema.optional(Schema.Boolean), command: Schema.optional(Schema.mutable(Schema.Array(Schema.String))), environment: Schema.optional(Schema.Record(Schema.String, Schema.String)), extensions: Schema.optional(Schema.mutable(Schema.Array(Schema.String))), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) -export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)]).pipe( - withStatics((s) => ({ zod: zod(s) })), -) +export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)]) export type Info = Schema.Schema.Type diff --git a/packages/opencode/src/config/layout.ts b/packages/opencode/src/config/layout.ts index a5299ea95..3ac63576d 100644 --- a/packages/opencode/src/config/layout.ts +++ b/packages/opencode/src/config/layout.ts @@ -1,10 +1,6 @@ import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" -export const Layout = Schema.Literals(["auto", "stretch"]) - .annotate({ identifier: "LayoutConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Layout = Schema.Literals(["auto", "stretch"]).annotate({ identifier: "LayoutConfig" }) export type Layout = Schema.Schema.Type export * as ConfigLayout from "./layout" diff --git a/packages/opencode/src/config/mcp.ts b/packages/opencode/src/config/mcp.ts index bb4fd88f0..cf170b95f 100644 --- a/packages/opencode/src/config/mcp.ts +++ b/packages/opencode/src/config/mcp.ts @@ -1,6 +1,5 @@ import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { PositiveInt, withStatics } from "@opencode-ai/core/schema" +import { PositiveInt } from "@opencode-ai/core/schema" export const Local = Schema.Struct({ type: Schema.Literal("local").annotate({ description: "Type of MCP server connection" }), @@ -16,9 +15,7 @@ export const Local = Schema.Struct({ timeout: Schema.optional(PositiveInt).annotate({ description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.", }), -}) - .annotate({ identifier: "McpLocalConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "McpLocalConfig" }) export type Local = Schema.Schema.Type export const OAuth = Schema.Struct({ @@ -32,9 +29,7 @@ export const OAuth = Schema.Struct({ redirectUri: Schema.optional(Schema.String).annotate({ description: "OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback).", }), -}) - .annotate({ identifier: "McpOAuthConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "McpOAuthConfig" }) export type OAuth = Schema.Schema.Type export const Remote = Schema.Struct({ @@ -52,14 +47,10 @@ export const Remote = Schema.Struct({ timeout: Schema.optional(PositiveInt).annotate({ description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.", }), -}) - .annotate({ identifier: "McpRemoteConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "McpRemoteConfig" }) export type Remote = Schema.Schema.Type -export const Info = Schema.Union([Local, Remote]) - .annotate({ discriminator: "type" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Info = Schema.Union([Local, Remote]).annotate({ discriminator: "type" }) export type Info = Schema.Schema.Type export * as ConfigMCP from "./mcp" diff --git a/packages/opencode/src/config/model-id.ts b/packages/opencode/src/config/model-id.ts index 26fa2e0b3..6cba3ecd2 100644 --- a/packages/opencode/src/config/model-id.ts +++ b/packages/opencode/src/config/model-id.ts @@ -1,7 +1,6 @@ import { Schema } from "effect" import z from "zod" -import { zod, ZodOverride } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" +import { ZodOverride } from "@opencode-ai/core/effect-zod" // The original Zod schema carried an external $ref pointing at the models.dev // JSON schema. That external reference is not a named SDK component — it is a @@ -9,6 +8,6 @@ import { withStatics } from "@opencode-ai/core/schema" // from AST metadata. Preserve the exact original Zod via ZodOverride. export const ConfigModelID = Schema.String.annotate({ [ZodOverride]: z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type ConfigModelID = Schema.Schema.Type diff --git a/packages/opencode/src/config/parse.ts b/packages/opencode/src/config/parse.ts index f964ed4e1..d4048cf17 100644 --- a/packages/opencode/src/config/parse.ts +++ b/packages/opencode/src/config/parse.ts @@ -2,12 +2,10 @@ export * as ConfigParse from "./parse" import { type ParseError as JsoncParseError, parse as parseJsoncImpl, printParseErrorCode } from "jsonc-parser" import { Cause, Exit, Schema as EffectSchema, SchemaIssue } from "effect" -import z from "zod" +import type z from "zod" import type { DeepMutable } from "@opencode-ai/core/schema" import { InvalidError, JsonError } from "./error" -type ZodSchema = z.ZodType - export function jsonc(text: string, filepath: string): unknown { const errors: JsoncParseError[] = [] const data = parseJsoncImpl(text, errors, { allowTrailingComma: true }) @@ -35,17 +33,7 @@ export function jsonc(text: string, filepath: string): unknown { return data } -export function schema(schema: ZodSchema, data: unknown, source: string): T { - const parsed = schema.safeParse(data) - if (parsed.success) return parsed.data - - throw new InvalidError({ - path: source, - issues: parsed.error.issues, - }) -} - -export function effectSchema>( +export function schema>( schema: S, data: unknown, source: string, diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts index 8c5f85499..a04b404e8 100644 --- a/packages/opencode/src/config/permission.ts +++ b/packages/opencode/src/config/permission.ts @@ -1,21 +1,13 @@ export * as ConfigPermission from "./permission" import { Schema, SchemaGetter } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" -export const Action = Schema.Literals(["ask", "allow", "deny"]) - .annotate({ identifier: "PermissionActionConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Action = Schema.Literals(["ask", "allow", "deny"]).annotate({ identifier: "PermissionActionConfig" }) export type Action = Schema.Schema.Type -export const Object = Schema.Record(Schema.String, Action) - .annotate({ identifier: "PermissionObjectConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Object = Schema.Record(Schema.String, Action).annotate({ identifier: "PermissionObjectConfig" }) export type Object = Schema.Schema.Type -export const Rule = Schema.Union([Action, Object]) - .annotate({ identifier: "PermissionRuleConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Rule = Schema.Union([Action, Object]).annotate({ identifier: "PermissionRuleConfig" }) export type Rule = Schema.Schema.Type // Known permission keys get explicit types in the Effect schema for generated @@ -62,12 +54,6 @@ export const Info = InputSchema.pipe( // of the same rules. encode: SchemaGetter.passthrough({ strict: false }), }), -) - .annotate({ identifier: "PermissionConfig" }) - .pipe( - // Walker already emits the decodeTo transform into the derived zod (see - // `encoded()` in effect-zod.ts), so just expose that directly. - withStatics((s) => ({ zod: zod(s) })), - ) +).annotate({ identifier: "PermissionConfig" }) type _Info = Schema.Schema.Type export type Info = { -readonly [K in keyof _Info]: _Info[K] } diff --git a/packages/opencode/src/config/plugin.ts b/packages/opencode/src/config/plugin.ts index b1e3ec6f4..c70442427 100644 --- a/packages/opencode/src/config/plugin.ts +++ b/packages/opencode/src/config/plugin.ts @@ -6,7 +6,7 @@ import { zod } from "@opencode-ai/core/effect-zod" import { withStatics } from "@opencode-ai/core/schema" import path from "path" -export const Options = Schema.Record(Schema.String, Schema.Unknown).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Options = Schema.Record(Schema.String, Schema.Unknown) export type Options = Schema.Schema.Type // Spec is the user-config value: either just a plugin identifier, or the identifier plus inline options. diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts index af9aac696..5635512ce 100644 --- a/packages/opencode/src/config/provider.ts +++ b/packages/opencode/src/config/provider.ts @@ -1,6 +1,5 @@ import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { PositiveInt, withStatics } from "@opencode-ai/core/schema" +import { PositiveInt } from "@opencode-ai/core/schema" import { ModelStatus } from "@/provider/model-status" export const Model = Schema.Struct({ @@ -67,7 +66,7 @@ export const Model = Schema.Struct({ ), ).annotate({ description: "Variant-specific configuration" }), ), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export const Info = Schema.Struct({ api: Schema.optional(Schema.String), @@ -106,9 +105,7 @@ export const Info = Schema.Struct({ ), ), models: Schema.optional(Schema.Record(Schema.String, Model)), -}) - .annotate({ identifier: "ProviderConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "ProviderConfig" }) export type Info = Schema.Schema.Type export * as ConfigProvider from "./provider" diff --git a/packages/opencode/src/config/reference.ts b/packages/opencode/src/config/reference.ts index 36a8faff7..b3dec491a 100644 --- a/packages/opencode/src/config/reference.ts +++ b/packages/opencode/src/config/reference.ts @@ -1,8 +1,6 @@ export * as ConfigReference from "./reference" import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" const Git = Schema.Struct({ repository: Schema.String.annotate({ @@ -21,7 +19,5 @@ const Local = Schema.Struct({ export const Entry = Schema.Union([Schema.String, Git, Local]).annotate({ identifier: "ReferenceConfigEntry" }) -export const Info = Schema.Record(Schema.String, Entry) - .annotate({ identifier: "ReferenceConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Info = Schema.Record(Schema.String, Entry).annotate({ identifier: "ReferenceConfig" }) export type Info = Schema.Schema.Type diff --git a/packages/opencode/src/config/server.ts b/packages/opencode/src/config/server.ts index 159ba0ce5..642adbe51 100644 --- a/packages/opencode/src/config/server.ts +++ b/packages/opencode/src/config/server.ts @@ -1,6 +1,5 @@ import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { PositiveInt, withStatics } from "@opencode-ai/core/schema" +import { PositiveInt } from "@opencode-ai/core/schema" export const Server = Schema.Struct({ port: Schema.optional(PositiveInt).annotate({ @@ -14,9 +13,7 @@ export const Server = Schema.Struct({ cors: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({ description: "Additional domains to allow for CORS", }), -}) - .annotate({ identifier: "ServerConfig" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "ServerConfig" }) export type Server = Schema.Schema.Type export * as ConfigServer from "./server" diff --git a/packages/opencode/src/config/skills.ts b/packages/opencode/src/config/skills.ts index f707e922e..38c0017d0 100644 --- a/packages/opencode/src/config/skills.ts +++ b/packages/opencode/src/config/skills.ts @@ -1,6 +1,4 @@ import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" export const Info = Schema.Struct({ paths: Schema.optional(Schema.Array(Schema.String)).annotate({ @@ -9,7 +7,7 @@ export const Info = Schema.Struct({ urls: Schema.optional(Schema.Array(Schema.String)).annotate({ description: "URLs to fetch skills from (e.g., https://example.com/.well-known/skills/)", }), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type Info = Schema.Schema.Type diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index a647dc099..12ce5f581 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -13,8 +13,8 @@ import { spawn as lspspawn } from "./launch" import { Effect, Layer, Context, Schema } from "effect" import { InstanceState } from "@/effect/instance-state" import { containsPath } from "@/project/instance-context" -import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema" -import { zod, ZodOverride } from "@opencode-ai/core/effect-zod" +import { NonNegativeInt } from "@opencode-ai/core/schema" +import { ZodOverride } from "@opencode-ai/core/effect-zod" const log = Log.create({ service: "lsp" }) @@ -30,9 +30,7 @@ const Position = Schema.Struct({ export const Range = Schema.Struct({ start: Position, end: Position, -}) - .annotate({ identifier: "Range" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Range" }) export type Range = typeof Range.Type export const Symbol = Schema.Struct({ @@ -42,9 +40,7 @@ export const Symbol = Schema.Struct({ uri: Schema.String, range: Range, }), -}) - .annotate({ identifier: "Symbol" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Symbol" }) export type Symbol = typeof Symbol.Type export const DocumentSymbol = Schema.Struct({ @@ -53,9 +49,7 @@ export const DocumentSymbol = Schema.Struct({ kind: NonNegativeInt, range: Range, selectionRange: Range, -}) - .annotate({ identifier: "DocumentSymbol" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "DocumentSymbol" }) export type DocumentSymbol = typeof DocumentSymbol.Type export const Status = Schema.Struct({ @@ -65,9 +59,7 @@ export const Status = Schema.Struct({ status: Schema.Literals(["connected", "error"]).annotate({ [ZodOverride]: z.union([z.literal("connected"), z.literal("error")]), }), -}) - .annotate({ identifier: "LSPStatus" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "LSPStatus" }) export type Status = typeof Status.Type enum SymbolKind { diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index ed74c648a..db43412f7 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -31,8 +31,6 @@ import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { zod as effectZod } from "@opencode-ai/core/effect-zod" -import { withStatics } from "@opencode-ai/core/schema" const log = Log.create({ service: "mcp" }) const DEFAULT_TIMEOUT = 30_000 @@ -52,9 +50,7 @@ export const Resource = Schema.Struct({ description: Schema.optional(Schema.String), mimeType: Schema.optional(Schema.String), client: Schema.String, -}) - .annotate({ identifier: "McpResource" }) - .pipe(withStatics((s) => ({ zod: effectZod(s) }))) +}).annotate({ identifier: "McpResource" }) export type Resource = Schema.Schema.Type export const ToolsChanged = BusEvent.define( @@ -104,9 +100,7 @@ export const Status = Schema.Union([ StatusFailed, StatusNeedsAuth, StatusNeedsClientRegistration, -]) - .annotate({ identifier: "MCPStatus", discriminator: "status" }) - .pipe(withStatics((s) => ({ zod: effectZod(s) }))) +]).annotate({ identifier: "MCPStatus", discriminator: "status" }) export type Status = Schema.Schema.Type // Store transports for OAuth servers to allow finishing auth diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index f4bd2e2cc..2f0813aff 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -7,9 +7,7 @@ import { MessageID, SessionID } from "@/session/schema" import { PermissionTable } from "@/session/session.sql" import { Database } from "@/storage/db" import { eq } from "drizzle-orm" -import { zod } from "@opencode-ai/core/effect-zod" import * as Log from "@opencode-ai/core/util/log" -import { withStatics } from "@opencode-ai/core/schema" import { Wildcard } from "@/util/wildcard" import { Deferred, Effect, Layer, Schema, Context } from "effect" import os from "os" @@ -18,23 +16,17 @@ import { PermissionID } from "./schema" const log = Log.create({ service: "permission" }) -export const Action = Schema.Literals(["allow", "deny", "ask"]) - .annotate({ identifier: "PermissionAction" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Action = Schema.Literals(["allow", "deny", "ask"]).annotate({ identifier: "PermissionAction" }) export type Action = Schema.Schema.Type export const Rule = Schema.Struct({ permission: Schema.String, pattern: Schema.String, action: Action, -}) - .annotate({ identifier: "PermissionRule" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "PermissionRule" }) export type Rule = Schema.Schema.Type -export const Ruleset = Schema.mutable(Schema.Array(Rule)) - .annotate({ identifier: "PermissionRuleset" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Ruleset = Schema.mutable(Schema.Array(Rule)).annotate({ identifier: "PermissionRuleset" }) export type Ruleset = Schema.Schema.Type export class Request extends Schema.Class("PermissionRequest")({ @@ -50,11 +42,9 @@ export class Request extends Schema.Class("PermissionRequest")({ callID: Schema.String, }), ), -}) { - static readonly zod = zod(this) -} +}) {} -export const Reply = Schema.Literals(["once", "always", "reject"]).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Reply = Schema.Literals(["once", "always", "reject"]) export type Reply = Schema.Schema.Type const reply = { @@ -62,17 +52,13 @@ const reply = { message: Schema.optional(Schema.String), } -export const ReplyBody = Schema.Struct(reply) - .annotate({ identifier: "PermissionReplyBody" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +export const ReplyBody = Schema.Struct(reply).annotate({ identifier: "PermissionReplyBody" }) export type ReplyBody = Schema.Schema.Type export class Approval extends Schema.Class("PermissionApproval")({ projectID: ProjectID, patterns: Schema.Array(Schema.String), -}) { - static readonly zod = zod(this) -} +}) {} export const Event = { Asked: BusEvent.define("permission.asked", Request), @@ -114,17 +100,13 @@ export const AskInput = Schema.Struct({ ...Request.fields, id: Schema.optional(PermissionID), ruleset: Ruleset, -}) - .annotate({ identifier: "PermissionAskInput" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "PermissionAskInput" }) export type AskInput = Schema.Schema.Type export const ReplyInput = Schema.Struct({ requestID: PermissionID, ...reply, -}) - .annotate({ identifier: "PermissionReplyInput" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "PermissionReplyInput" }) export type ReplyInput = Schema.Schema.Type export interface Interface { diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 91d272ea6..643685539 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -17,8 +17,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { zod } from "@opencode-ai/core/effect-zod" -import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema" +import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema" import { serviceUse } from "@/effect/service-use" const log = Log.create({ service: "project" }) @@ -52,9 +51,7 @@ export const Info = Schema.Struct({ commands: optionalOmitUndefined(ProjectCommands), time: ProjectTime, sandboxes: Schema.Array(Schema.String), -}) - .annotate({ identifier: "Project" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Project" }) export type Info = Types.DeepMutable> export const Event = { @@ -100,9 +97,7 @@ export const UpdatePayload = Schema.Struct({ name: Schema.optional(Schema.String), icon: Schema.optional(ProjectIcon), commands: Schema.optional(ProjectCommands), -}) - .annotate({ identifier: "ProjectUpdateInput" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "ProjectUpdateInput" }) export type UpdatePayload = Types.DeepMutable> // --------------------------------------------------------------------------- diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index 092444c44..5a477e02b 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -6,8 +6,6 @@ import { InstanceState } from "@/effect/instance-state" import { FileWatcher } from "@/file/watcher" import { Git } from "@/git" import * as Log from "@opencode-ai/core/util/log" -import { zod, zodObject } from "@opencode-ai/core/effect-zod" -import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema" const log = Log.create({ service: "vcs" }) const PATCH_CONTEXT_LINES = 2_147_483_647 @@ -208,7 +206,7 @@ const track = Effect.fnUntraced(function* (git: Git.Interface, cwd: string, ref: return yield* diffAgainstRef(git, cwd, ref) }) -export const Mode = Schema.Literals(["git", "branch"]).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Mode = Schema.Literals(["git", "branch"]) export type Mode = Schema.Schema.Type export const Event = { @@ -223,9 +221,7 @@ export const Event = { export const Info = Schema.Struct({ branch: Schema.optional(Schema.String), default_branch: Schema.optional(Schema.String), -}) - .annotate({ identifier: "VcsInfo" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "VcsInfo" }) export type Info = Schema.Schema.Type export const FileDiff = Schema.Struct({ @@ -237,9 +233,7 @@ export const FileDiff = Schema.Struct({ additions: Schema.Finite, deletions: Schema.Finite, status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])), -}) - .annotate({ identifier: "VcsFileDiff" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "VcsFileDiff" }) export type FileDiff = Schema.Schema.Type export const FileStatus = Schema.Struct({ @@ -247,19 +241,17 @@ export const FileStatus = Schema.Struct({ additions: Schema.Finite, deletions: Schema.Finite, status: Schema.Literals(["added", "deleted", "modified"]), -}) - .annotate({ identifier: "VcsFileStatus" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "VcsFileStatus" }) export type FileStatus = Schema.Schema.Type export const ApplyInput = Schema.Struct({ patch: Schema.String, -}).pipe(withStatics((s) => ({ zod: zod(s), zodObject: zodObject(s) }))) +}) export type ApplyInput = Schema.Schema.Type export const ApplyResult = Schema.Struct({ applied: Schema.Boolean, -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type ApplyResult = Schema.Schema.Type export class PatchApplyError extends Schema.TaggedErrorClass()("VcsPatchApplyError", { diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 135df6fec..42b94ffcc 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,9 +1,8 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" import { Auth } from "@/auth" import { InstanceState } from "@/effect/instance-state" -import { zod } from "@opencode-ai/core/effect-zod" import { namedSchemaError } from "@/util/named-schema-error" -import { optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema" +import { optionalOmitUndefined } from "@opencode-ai/core/schema" import { Plugin } from "../plugin" import { ProviderID } from "./schema" import { Array as Arr, Effect, Layer, Record, Result, Context, Schema } from "effect" @@ -42,31 +41,27 @@ export class Method extends Schema.Class("ProviderAuthMethod")({ type: Schema.Literals(["oauth", "api"]), label: Schema.String, prompts: optionalOmitUndefined(Schema.Array(Prompt)), -}) { - static readonly zod = zod(this) -} +}) {} -export const Methods = Schema.Record(Schema.String, Schema.Array(Method)).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const Methods = Schema.Record(Schema.String, Schema.Array(Method)) export type Methods = typeof Methods.Type export class Authorization extends Schema.Class("ProviderAuthAuthorization")({ url: Schema.String, method: Schema.Literals(["auto", "code"]), instructions: Schema.String, -}) { - static readonly zod = zod(this) -} +}) {} export const AuthorizeInput = Schema.Struct({ method: Schema.Finite.annotate({ description: "Auth method index" }), inputs: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({ description: "Prompt inputs" }), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type AuthorizeInput = Schema.Schema.Type export const CallbackInput = Schema.Struct({ method: Schema.Finite.annotate({ description: "Auth method index" }), code: Schema.optional(Schema.String).annotate({ description: "OAuth authorization code" }), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type CallbackInput = Schema.Schema.Type export const OauthMissing = namedSchemaError("ProviderAuthOauthMissing", { providerID: ProviderID }) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index c27b69b6a..236f14de7 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -13,7 +13,6 @@ import { Auth } from "../auth" import { Env } from "../env" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { Flag } from "@opencode-ai/core/flag/flag" -import { zod } from "@opencode-ai/core/effect-zod" import { namedSchemaError } from "@/util/named-schema-error" import { iife } from "@/util/iife" import { Global } from "@opencode-ai/core/global" @@ -24,7 +23,7 @@ import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { isRecord } from "@/util/record" -import { optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema" +import { optionalOmitUndefined } from "@opencode-ai/core/schema" import * as ProviderTransform from "./transform" import { ModelID, ProviderID } from "./schema" @@ -903,9 +902,7 @@ export const Model = Schema.Struct({ headers: Schema.Record(Schema.String, Schema.String), release_date: Schema.String, variants: optionalOmitUndefined(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))), -}) - .annotate({ identifier: "Model" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Model" }) export type Model = Types.DeepMutable> export const Info = Schema.Struct({ @@ -916,9 +913,7 @@ export const Info = Schema.Struct({ key: optionalOmitUndefined(Schema.String), options: Schema.Record(Schema.String, Schema.Any), models: Schema.Record(Schema.String, Model), -}) - .annotate({ identifier: "Provider" }) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).annotate({ identifier: "Provider" }) export type Info = Types.DeepMutable> const DefaultModelIDs = Schema.Record(Schema.String, Schema.String) @@ -927,13 +922,13 @@ export const ListResult = Schema.Struct({ all: Schema.Array(Info), default: DefaultModelIDs, connected: Schema.Array(Schema.String), -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type ListResult = Types.DeepMutable> export const ConfigProvidersResult = Schema.Struct({ providers: Schema.Array(Info), default: DefaultModelIDs, -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) export type ConfigProvidersResult = Types.DeepMutable> export function toPublicInfo(provider: Info): Info { diff --git a/packages/opencode/src/provider/schema.ts b/packages/opencode/src/provider/schema.ts index 757b70f3f..db05b4784 100644 --- a/packages/opencode/src/provider/schema.ts +++ b/packages/opencode/src/provider/schema.ts @@ -1,6 +1,5 @@ import { Schema } from "effect" -import { zod } from "@opencode-ai/core/effect-zod" import { withStatics } from "@opencode-ai/core/schema" const providerIdSchema = Schema.String.pipe(Schema.brand("ProviderID")) @@ -9,7 +8,6 @@ export type ProviderID = typeof providerIdSchema.Type export const ProviderID = providerIdSchema.pipe( withStatics((schema: typeof providerIdSchema) => ({ - zod: zod(schema), // Well-known providers opencode: schema.make("opencode"), anthropic: schema.make("anthropic"), @@ -29,8 +27,4 @@ const modelIdSchema = Schema.String.pipe(Schema.brand("ModelID")) export type ModelID = typeof modelIdSchema.Type -export const ModelID = modelIdSchema.pipe( - withStatics((schema: typeof modelIdSchema) => ({ - zod: zod(schema), - })), -) +export const ModelID = modelIdSchema diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index c041462ad..94182f1a2 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -3,9 +3,7 @@ import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" import { SessionID, MessageID } from "@/session/schema" -import { zod } from "@opencode-ai/core/effect-zod" import * as Log from "@opencode-ai/core/util/log" -import { withStatics } from "@opencode-ai/core/schema" import { QuestionID } from "./schema" const log = Log.create({ service: "question" }) @@ -19,9 +17,7 @@ export class Option extends Schema.Class
0.1, }} From 061efc6cf260762677bdbac9261725a2da4fdbea Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 20:30:23 -0400 Subject: [PATCH 055/378] Fix run JSON output draining (#26955) --- packages/opencode/src/cli/cmd/run.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index bca89c3ca..7011b51eb 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -719,6 +719,7 @@ export const RunCommand = effectCmd({ } } } + return error } const cwd = args.attach ? (directory ?? sess.directory ?? (await current(sdk))) : (directory ?? root) const client = args.attach ? attachSDK(cwd) : sdk @@ -730,10 +731,7 @@ export const RunCommand = effectCmd({ if (!args.interactive) { const events = await client.event.subscribe() - loop(client, events).catch((e) => { - console.error(e) - process.exit(1) - }) + const completed = loop(client, events) if (args.command) { await client.session.command({ @@ -744,6 +742,8 @@ export const RunCommand = effectCmd({ arguments: message, variant: args.variant, }) + const error = await completed + if (error) process.exitCode = 1 return } @@ -755,6 +755,8 @@ export const RunCommand = effectCmd({ variant: args.variant, parts: [...files, { type: "text", text: message }], }) + const error = await completed + if (error) process.exitCode = 1 return } From ec9584177fc7a00b9f02c74ee4fde7850cee4825 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 20:32:27 -0400 Subject: [PATCH 056/378] docs(test): plan Effect test migration (#26954) --- .../opencode/test/EFFECT_TEST_MIGRATION.md | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 packages/opencode/test/EFFECT_TEST_MIGRATION.md diff --git a/packages/opencode/test/EFFECT_TEST_MIGRATION.md b/packages/opencode/test/EFFECT_TEST_MIGRATION.md new file mode 100644 index 000000000..20472a631 --- /dev/null +++ b/packages/opencode/test/EFFECT_TEST_MIGRATION.md @@ -0,0 +1,215 @@ +# Effect Test Migration Plan + +This document describes how to move opencode tests out of Promise-land and into the shared `testEffect` pattern. + +## Target Pattern + +Every test file that exercises Effect services should have one local runner near the top: + +```ts +const it = testEffect(layer) +``` + +Then each test should use one of the runner methods: + +```ts +it.effect("pure service behavior", () => + Effect.gen(function* () { + const service = yield* SomeService.Service + expect(yield* service.run()).toEqual("ok") + }), +) + +it.instance("instance-local behavior", () => + Effect.gen(function* () { + const test = yield* TestInstance + // test.directory is a scoped temp opencode instance + }), +) + +it.live("live filesystem or process behavior", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + // real clock / fs / git / process work + }), +) +``` + +Use `it.effect` for pure Effect code that should run with `TestClock` and `TestConsole`. +Use `it.instance` when the test needs one scoped opencode instance. +Use `it.live` when the test depends on real time, filesystem mtimes, git, child processes, servers, file watchers, or OS behavior. + +## Anti-Patterns To Remove + +Avoid these in tests that already target Effect services: + +- `test(..., async () => Effect.runPromise(...))` +- local `run(...)`, `load(...)`, `svc(...)`, or `runtime.runPromise(...)` wrappers that only provide a layer +- `tmpdir()` plus `WithInstance.provide(...)` in Promise test bodies +- custom `ManagedRuntime.make(...)` in test files +- Promise `try/catch` around Effect failures +- `Promise.withResolvers`, `Bun.sleep`, or `setTimeout` for synchronization when `Deferred`, `Fiber`, or `Effect.sleep` can express the same behavior + +Promise helpers are acceptable at the boundary for non-Effect APIs, but they should be yielded from an Effect body with `Effect.promise(...)` rather than becoming the test harness. + +## Layer Rules + +Compose tests from open service layers, not closed `defaultLayer` graphs when a dependency needs replacing. + +Good: + +```ts +const layer = Config.layer.pipe( + Layer.provide(AppFileSystem.defaultLayer), + Layer.provide(Env.defaultLayer), + Layer.provide(AuthTest.empty), + Layer.provide(AccountTest.empty), + Layer.provide(NpmTest.noop), +) +``` + +Avoid using a fully closed layer and hoping to override an inner dependency later. Once `Agent.defaultLayer` has already provided `Config.defaultLayer`, tests cannot cleanly swap the `Npm.Service` used by that config layer. + +Prefer small reusable fake boundary layers in `test/fake/*`: + +```ts +AuthTest.empty +AccountTest.empty +NpmTest.noop +SkillTest.empty +ProviderTest.fake().layer +``` + +Do not add generic test-layer builders until repeated local compositions prove the need. Shared fake boundary services are the first reusable unit. Pre-composed subtrees such as `AgentTest.withPlugins` should come later, only after the same graph appears in multiple files. + +## Fixture Rules + +Use Effect-aware fixtures from `test/fixture/fixture.ts`: + +- `TestInstance` inside `it.instance(...)` for the current temp instance path +- `tmpdirScoped(...)` inside `Effect.gen` for additional temp directories +- `provideInstance(dir)(effect)` when one test needs to switch instance context +- `provideTmpdirInstance((dir) => effect, options)` when a live test needs custom instance setup or multiple instance scopes +- `disposeAllInstances()` in `afterEach` only for integration tests that intentionally touch shared instance registries + +Use finalizers only as a temporary bridge for existing global mutations: + +```ts +yield* Effect.acquireUseRelease( + Effect.sync(() => { + const previous = process.env.MY_FLAG + process.env.MY_FLAG = "1" + return previous + }), + () => testBody, + (previous) => + Effect.sync(() => { + if (previous === undefined) delete process.env.MY_FLAG + else process.env.MY_FLAG = previous + }), +) +``` + +TODO: eliminate this pattern over time. Tests should not toggle process-global flags or env vars when the behavior can be modeled with services. Prefer moving flag/env reads behind injectable services such as `Config.Service`, `Env.Service`, or focused test layers, then provide the desired test value through the layer graph instead of mutating `process.env` or `Global.Path`. + +## Conversion Recipe + +1. Identify the real service under test and its open `*.layer`. +2. Build one top-level `layer` with real dependencies where they are relevant and `test/fake/*` layers at slow or external boundaries. +3. Replace local Promise wrappers with Effect helpers: + +```ts +const run = Effect.fn("MyTest.run")(function* (input: Input) { + const service = yield* MyService.Service + return yield* service.run(input) +}) +``` + +4. Convert `test(..., async () => { ... })` to `it.effect`, `it.instance`, or `it.live`. +5. Move `await` calls inside `Effect.gen` as `yield*` calls. +6. Replace `await using tmp = await tmpdir(...)` with `yield* tmpdirScoped(...)` when the temp directory is inside an Effect test. +7. Replace `WithInstance.provide({ directory, fn })` with `it.instance(...)`, `provideInstance(directory)(effect)`, or `provideTmpdirInstance(...)`. +8. Replace Promise failure assertions with Effect assertions: + +```ts +const exit = yield* run(input).pipe(Effect.exit) +expect(Exit.isFailure(exit)).toBe(true) +``` + +This is correct but still verbose. Track repeated assertion shapes during migration so we can add small test assertion helpers later instead of copying low-level `Exit` plumbing everywhere. + +9. Keep concurrency concurrent by using `Effect.forkScoped`, `Fiber.join`, `Deferred`, or `Effect.all(..., { concurrency: "unbounded" })` instead of serializing formerly parallel Promise work. +10. Run the focused test file and `bun typecheck` from `packages/opencode`. + +## Good Examples + +Use these files as models: + +- `test/tool/write.test.ts`: strong `it.instance` tests, top-level `testEffect(...)`, and Effect-native test helpers. +- `test/effect/instance-state.test.ts`: good `it.live` use for scoped directories, instance switching, reload/disposal, and concurrency. +- `test/bus/bus-effect.test.ts`: good `Deferred`, streams, and scoped fibers. +- `test/tool/truncation.test.ts`: good configured runners and concise live service tests. +- `test/tool/repo_clone.test.ts`: good live git integration while staying inside Effect fixtures. +- `test/server/httpapi-instance.test.ts`: good scoped integration layer setup and live HTTP assertions. +- `test/account/service.test.ts`: good service-level live tests, `Effect.flip`, typed errors, and fake HTTP clients. +- `test/agent/plugin-agent-regression.test.ts`: good example of open real service layers plus reusable fake boundary layers. + +## Current Promise-Land Hotspots + +Start with files that already exercise Effect services but still manually run Promises: + +- `test/config/config.test.ts`: many `Effect.runPromise`, `tmpdir()`, and `WithInstance.provide(...)` patterns despite already having `const it = testEffect(layer)`. +- `test/tool/shell.test.ts`: custom `ManagedRuntime`, Promise test helpers, and instance setup around shell execution. +- `test/tool/edit.test.ts`: manual runtime helpers and Promise concurrency patterns that should become fibers/deferreds. +- `test/session/messages-pagination.test.ts`: local Promise service facade over `Session.defaultLayer`. +- `test/snapshot/snapshot.test.ts`: Promise helper with `provideInstance` around snapshot operations. +- `test/file/index.test.ts`: Promise wrappers for `File.Service` plus repeated temp instance setup. +- `test/provider/provider.test.ts`: `AppRuntime.runPromise` helpers and mutable env/config setup. +- `test/project/vcs.test.ts`: Promise event waiting and `AppRuntime.runPromise` around VCS service calls. + +## Migration Order + +1. Convert one small file with straightforward service calls and no race behavior. +2. Convert `config.test.ts` incrementally by cluster, not in one PR. +3. Extract additional `test/fake/*` boundary layers only when a second test needs the same fake. +4. Convert files with concurrency or watchers after the simple files, preserving timing semantics with `Deferred` and fibers. +5. Leave pure non-Effect utility tests alone unless converting the underlying code to Effect. + +## Claimable Checklist + +Use this as a migration queue. Each checkbox should be safe for one agent or one PR unless the notes say otherwise. Agents should claim one item, convert only that file or cluster, run the focused test file, run `bun typecheck`, and update this checklist in the PR description or follow-up note. + +- [ ] `test/file/index.test.ts`: straightforward service wrapper cleanup. Replace local Promise helpers with Effect helpers and use `it.instance` / `it.live` around existing temp instance cases. +- [ ] `test/session/messages-pagination.test.ts`: convert the local `run(...)` / `svc(...)` facade to `testEffect(Session.defaultLayer...)` and direct service yields. Good early target. +- [ ] `test/snapshot/snapshot.test.ts`: convert snapshot operations to `it.live` with `tmpdirScoped` / `provideInstance`. Keep git/filesystem behavior live. +- [ ] `test/project/vcs.test.ts`: convert `AppRuntime.runPromise` service calls first. Leave event/watcher timing intact until the first Effect version is stable. +- [ ] `test/provider/provider.test.ts` cluster 1: convert provider service tests that only read config/env and do not mutate global state heavily. +- [ ] `test/provider/provider.test.ts` cluster 2: convert tests with env/config mutation after introducing or reusing service-backed test seams. +- [ ] `test/tool/shell.test.ts`: replace custom `ManagedRuntime` with `testEffect`, keep as `it.live`, and preserve process behavior. +- [ ] `test/tool/edit.test.ts` cluster 1: convert straightforward edit/read/write cases and remove manual runtime helpers. +- [ ] `test/tool/edit.test.ts` cluster 2: convert concurrency/race tests using `Deferred`, fibers, and `Effect.all` without serializing behavior. +- [ ] `test/config/config.test.ts` setup pass: replace inline fake layers with shared `test/fake/*` layers where possible and turn Promise helpers into Effect helpers. +- [ ] `test/config/config.test.ts` cluster 1: convert simple config load/merge tests that only need one instance. +- [ ] `test/config/config.test.ts` cluster 2: convert managed/global config tests that mutate `Global.Path` or managed config directories. Prefer service seams; use finalizers only as a bridge. +- [ ] `test/config/config.test.ts` cluster 3: convert plugin/dependency tests after ensuring `NpmTest.noop` or explicit fake NPM layers are used. +- [ ] `test/config/config.test.ts` cluster 4: convert remote/account/provider config tests after isolating auth/account/env dependencies through layers. +- [ ] Audit remaining `Effect.runPromise` in `packages/opencode/test/**/*.ts` and create follow-up checklist entries for any missed files. +- [ ] Audit remaining `WithInstance.provide` in `packages/opencode/test/**/*.ts` and convert cases that can use `it.instance` or `provideInstance` inside Effect. +- [ ] Audit repeated `Exit` / `Cause` assertion shapes and propose `test/lib/effect-assert.ts` helpers if at least three files repeat the same pattern. + +Parallelization notes: + +- The first four items are mostly independent and good for parallel agents. +- `provider.test.ts`, `tool/edit.test.ts`, and `config.test.ts` should be split by cluster so agents do not edit the same file concurrently. +- Any new fake boundary layer under `test/fake/*` should be small and independently useful. Do not add a fake just for one assertion unless it removes a real external dependency. +- Do not combine assertion-helper design with file migrations. First collect repeated shapes, then add helpers in a separate pass. + +## Effectified Test Rough Edges + +Track patterns that are technically Effect-native but still too noisy. These should become a second cleanup pass after the Promise-land migration is underway. + +- Failure assertions against `Exit` / `Cause` are often verbose. Consider helpers such as `expectEffectFailure(effect)`, `expectTaggedError(effect, Tag)`, or custom Bun matchers if the same shapes repeat. +- Some tests still need `Effect.promise(...)` around Node/Bun filesystem helpers. Prefer Effect platform services when the surrounding code already uses them, but do not block migrations on perfect filesystem abstraction. +- Scoped global mutation with `process.env`, `Global.Path`, or flags should disappear behind injectable services over time. +- Layer composition can be noisy when a test needs a real service subtree plus fake boundaries. Keep extracting small `test/fake/*` boundary layers before inventing larger builders. +- Concurrency tests can become harder to read after replacing Promise resolvers with `Deferred` and fibers. Look for repeated patterns that deserve named helpers. From 8015ff7ca5c17e272181a915be1c5cec0b26e88b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 00:33:34 +0000 Subject: [PATCH 057/378] chore: generate --- .../opencode/test/EFFECT_TEST_MIGRATION.md | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/opencode/test/EFFECT_TEST_MIGRATION.md b/packages/opencode/test/EFFECT_TEST_MIGRATION.md index 20472a631..60cd33264 100644 --- a/packages/opencode/test/EFFECT_TEST_MIGRATION.md +++ b/packages/opencode/test/EFFECT_TEST_MIGRATION.md @@ -95,19 +95,20 @@ Use Effect-aware fixtures from `test/fixture/fixture.ts`: Use finalizers only as a temporary bridge for existing global mutations: ```ts -yield* Effect.acquireUseRelease( - Effect.sync(() => { - const previous = process.env.MY_FLAG - process.env.MY_FLAG = "1" - return previous - }), - () => testBody, - (previous) => +yield * + Effect.acquireUseRelease( Effect.sync(() => { - if (previous === undefined) delete process.env.MY_FLAG - else process.env.MY_FLAG = previous + const previous = process.env.MY_FLAG + process.env.MY_FLAG = "1" + return previous }), -) + () => testBody, + (previous) => + Effect.sync(() => { + if (previous === undefined) delete process.env.MY_FLAG + else process.env.MY_FLAG = previous + }), + ) ``` TODO: eliminate this pattern over time. Tests should not toggle process-global flags or env vars when the behavior can be modeled with services. Prefer moving flag/env reads behind injectable services such as `Config.Service`, `Env.Service`, or focused test layers, then provide the desired test value through the layer graph instead of mutating `process.env` or `Global.Path`. @@ -132,7 +133,7 @@ const run = Effect.fn("MyTest.run")(function* (input: Input) { 8. Replace Promise failure assertions with Effect assertions: ```ts -const exit = yield* run(input).pipe(Effect.exit) +const exit = yield * run(input).pipe(Effect.exit) expect(Exit.isFailure(exit)).toBe(true) ``` From fbd52ca2f4fcbeb23f070b8a010cda40129f207b Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 20:39:31 -0400 Subject: [PATCH 058/378] test(file): migrate file tests to Effect runner (#26959) --- packages/opencode/test/file/index.test.ts | 1594 ++++++++++----------- 1 file changed, 740 insertions(+), 854 deletions(-) diff --git a/packages/opencode/test/file/index.test.ts b/packages/opencode/test/file/index.test.ts index cdd2e211c..925084140 100644 --- a/packages/opencode/test/file/index.test.ts +++ b/packages/opencode/test/file/index.test.ts @@ -1,557 +1,489 @@ -import { afterEach, describe, test, expect } from "bun:test" +import { afterEach, describe, expect } from "bun:test" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { $ } from "bun" -import { Effect } from "effect" +import { Cause, Effect, Exit, Layer } from "effect" import path from "path" import fs from "fs/promises" import { File } from "../../src/file" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { Filesystem } from "@/util/filesystem" -import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, TestInstance, withTmpdirInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" afterEach(async () => { await disposeAllInstances() }) -const init = () => run(File.Service.use((svc) => svc.init())) -const run = (eff: Effect.Effect) => - Effect.runPromise(provideInstance(Instance.directory)(eff.pipe(Effect.provide(File.defaultLayer)))) -const status = () => run(File.Service.use((svc) => svc.status())) -const read = (file: string) => run(File.Service.use((svc) => svc.read(file))) -const list = (dir?: string) => run(File.Service.use((svc) => svc.list(dir))) -const search = (input: { query: string; limit?: number; dirs?: boolean; type?: "file" | "directory" }) => - run(File.Service.use((svc) => svc.search(input))) +const it = testEffect(Layer.mergeAll(File.defaultLayer, AppFileSystem.defaultLayer)) + +const init = Effect.fn("FileTest.init")(function* () { + const file = yield* File.Service + return yield* file.init() +}) + +const status = Effect.fn("FileTest.status")(function* () { + const file = yield* File.Service + return yield* file.status() +}) + +const read = Effect.fn("FileTest.read")(function* (input: string) { + const file = yield* File.Service + return yield* file.read(input) +}) + +const list = Effect.fn("FileTest.list")(function* (dir?: string) { + const file = yield* File.Service + return yield* file.list(dir) +}) + +const search = Effect.fn("FileTest.search")(function* (input: { + query: string + limit?: number + dirs?: boolean + type?: "file" | "directory" +}) { + const file = yield* File.Service + return yield* file.search(input) +}) + +const gitAddAll = (directory: string) => Effect.promise(() => $`git add .`.cwd(directory).quiet()) +const gitCommit = (directory: string, message: string) => + Effect.promise(() => $`git commit -m ${message}`.cwd(directory).quiet()) + +const failureMessage = (self: Effect.Effect) => + Effect.gen(function* () { + const exit = yield* self.pipe(Effect.exit) + if (Exit.isFailure(exit)) { + const error = Cause.squash(exit.cause) + return error instanceof Error ? error.message : String(error) + } + throw new Error("expected effect to fail") + }) + +const setupSearchableRepo = Effect.fn("FileTest.setupSearchableRepo")(function* (directory: string) { + const fsys = yield* AppFileSystem.Service + yield* fsys.writeWithDirs(path.join(directory, "index.ts"), "code") + yield* fsys.writeWithDirs(path.join(directory, "utils.ts"), "utils") + yield* fsys.writeWithDirs(path.join(directory, "readme.md"), "readme") + yield* fsys.writeWithDirs(path.join(directory, "src", "main.ts"), "main") + yield* fsys.writeWithDirs(path.join(directory, ".hidden", "secret.ts"), "secret") +}) describe("file/index Filesystem patterns", () => { describe("read() - text content", () => { - test("reads text file via Filesystem.readText()", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "test.txt") - await fs.writeFile(filepath, "Hello World", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("test.txt") - expect(result.type).toBe("text") - expect(result.content).toBe("Hello World") - }, - }) - }) - - test("reads with Filesystem.exists() check", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - // Non-existent file should return empty content - const result = await read("nonexistent.txt") - expect(result.type).toBe("text") - expect(result.content).toBe("") - }, - }) - }) - - test("trims whitespace from text content", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "test.txt") - await fs.writeFile(filepath, " content with spaces \n\n", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("test.txt") - expect(result.content).toBe("content with spaces") - }, - }) - }) - - test("handles empty text file", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "empty.txt") - await fs.writeFile(filepath, "", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("empty.txt") - expect(result.type).toBe("text") - expect(result.content).toBe("") - }, - }) - }) - - test("handles multi-line text files", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "multiline.txt") - await fs.writeFile(filepath, "line1\nline2\nline3", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("multiline.txt") - expect(result.content).toBe("line1\nline2\nline3") - }, - }) - }) + it.instance("reads text file via Filesystem.readText()", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.txt"), "Hello World", "utf-8")) + + const result = yield* read("test.txt") + expect(result.type).toBe("text") + expect(result.content).toBe("Hello World") + }), + ) + + it.instance("reads with Filesystem.exists() check", () => + Effect.gen(function* () { + const result = yield* read("nonexistent.txt") + expect(result.type).toBe("text") + expect(result.content).toBe("") + }), + ) + + it.instance("trims whitespace from text content", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "test.txt"), " content with spaces \n\n", "utf-8"), + ) + + const result = yield* read("test.txt") + expect(result.content).toBe("content with spaces") + }), + ) + + it.instance("handles empty text file", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "empty.txt"), "", "utf-8")) + + const result = yield* read("empty.txt") + expect(result.type).toBe("text") + expect(result.content).toBe("") + }), + ) + + it.instance("handles multi-line text files", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "multiline.txt"), "line1\nline2\nline3", "utf-8")) + + const result = yield* read("multiline.txt") + expect(result.content).toBe("line1\nline2\nline3") + }), + ) }) describe("read() - binary content", () => { - test("reads binary file via Filesystem.readArrayBuffer()", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "image.png") - const binaryContent = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) - await fs.writeFile(filepath, binaryContent) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("image.png") - expect(result.type).toBe("text") // Images return as text with base64 encoding - expect(result.encoding).toBe("base64") - expect(result.mimeType).toBe("image/png") - expect(result.content).toBe(binaryContent.toString("base64")) - }, - }) - }) - - test("returns empty for binary non-image files", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "binary.so") - await fs.writeFile(filepath, Buffer.from([0x7f, 0x45, 0x4c, 0x46]), "binary") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("binary.so") - expect(result.type).toBe("binary") - expect(result.content).toBe("") - }, - }) - }) + it.instance("reads binary file via Filesystem.readArrayBuffer()", () => + Effect.gen(function* () { + const test = yield* TestInstance + const binaryContent = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "image.png"), binaryContent)) + + const result = yield* read("image.png") + expect(result.type).toBe("text") + expect(result.encoding).toBe("base64") + expect(result.mimeType).toBe("image/png") + expect(result.content).toBe(binaryContent.toString("base64")) + }), + ) + + it.instance("returns empty for binary non-image files", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "binary.so"), Buffer.from([0x7f, 0x45, 0x4c, 0x46]))) + + const result = yield* read("binary.so") + expect(result.type).toBe("binary") + expect(result.content).toBe("") + }), + ) }) describe("read() - Filesystem.mimeType()", () => { - test("detects MIME type via Filesystem.mimeType()", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "test.json") - await fs.writeFile(filepath, '{"key": "value"}', "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - expect(await Filesystem.mimeType(filepath)).toContain("application/json") - - const result = await read("test.json") - expect(result.type).toBe("text") - }, - }) - }) - - test("handles various image MIME types", async () => { - await using tmp = await tmpdir() - const testCases = [ - { ext: "jpg", mime: "image/jpeg" }, - { ext: "png", mime: "image/png" }, - { ext: "gif", mime: "image/gif" }, - { ext: "webp", mime: "image/webp" }, - ] - - for (const { ext, mime } of testCases) { - const filepath = path.join(tmp.path, `test.${ext}`) - await fs.writeFile(filepath, Buffer.from([0x00, 0x00, 0x00, 0x00]), "binary") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - expect(await Filesystem.mimeType(filepath)).toContain(mime) - }, - }) - } - }) + it.instance("detects MIME type via Filesystem.mimeType()", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "test.json") + yield* Effect.promise(() => fs.writeFile(filepath, '{"key": "value"}', "utf-8")) + + expect(yield* Effect.promise(() => Filesystem.mimeType(filepath))).toContain("application/json") + + const result = yield* read("test.json") + expect(result.type).toBe("text") + }), + ) + + it.instance("handles various image MIME types", () => + Effect.gen(function* () { + const test = yield* TestInstance + const testCases = [ + { ext: "jpg", mime: "image/jpeg" }, + { ext: "png", mime: "image/png" }, + { ext: "gif", mime: "image/gif" }, + { ext: "webp", mime: "image/webp" }, + ] + + for (const testCase of testCases) { + const filepath = path.join(test.directory, `test.${testCase.ext}`) + yield* Effect.promise(() => fs.writeFile(filepath, Buffer.from([0x00, 0x00, 0x00, 0x00]))) + expect(yield* Effect.promise(() => Filesystem.mimeType(filepath))).toContain(testCase.mime) + } + }), + ) }) describe("list() - Filesystem.exists() and readText()", () => { - test("reads .gitignore via Filesystem.exists() and readText()", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const gitignorePath = path.join(tmp.path, ".gitignore") - await fs.writeFile(gitignorePath, "node_modules\ndist\n", "utf-8") - - // This is used internally in list() - expect(await Filesystem.exists(gitignorePath)).toBe(true) - - const content = await Filesystem.readText(gitignorePath) - expect(content).toContain("node_modules") - }, - }) - }) - - test("reads .ignore file similarly", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const ignorePath = path.join(tmp.path, ".ignore") - await fs.writeFile(ignorePath, "*.log\n.env\n", "utf-8") - - expect(await Filesystem.exists(ignorePath)).toBe(true) - expect(await Filesystem.readText(ignorePath)).toContain("*.log") - }, - }) - }) - - test("handles missing .gitignore gracefully", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const gitignorePath = path.join(tmp.path, ".gitignore") - expect(await Filesystem.exists(gitignorePath)).toBe(false) - - // list() should still work - const nodes = await list() + it.instance( + "reads .gitignore via Filesystem.exists() and readText()", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const gitignorePath = path.join(test.directory, ".gitignore") + yield* Effect.promise(() => fs.writeFile(gitignorePath, "node_modules\ndist\n", "utf-8")) + + expect(yield* Effect.promise(() => Filesystem.exists(gitignorePath))).toBe(true) + expect(yield* Effect.promise(() => Filesystem.readText(gitignorePath))).toContain("node_modules") + }), + { git: true }, + ) + + it.instance( + "reads .ignore file similarly", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const ignorePath = path.join(test.directory, ".ignore") + yield* Effect.promise(() => fs.writeFile(ignorePath, "*.log\n.env\n", "utf-8")) + + expect(yield* Effect.promise(() => Filesystem.exists(ignorePath))).toBe(true) + expect(yield* Effect.promise(() => Filesystem.readText(ignorePath))).toContain("*.log") + }), + { git: true }, + ) + + it.instance( + "handles missing .gitignore gracefully", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const gitignorePath = path.join(test.directory, ".gitignore") + expect(yield* Effect.promise(() => Filesystem.exists(gitignorePath))).toBe(false) + + const nodes = yield* list() expect(Array.isArray(nodes)).toBe(true) - }, - }) - }) + }), + { git: true }, + ) }) describe("File.changed() - Filesystem.readText() for untracked files", () => { - test("reads untracked files via Filesystem.readText()", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const untrackedPath = path.join(tmp.path, "untracked.txt") - await fs.writeFile(untrackedPath, "new content\nwith multiple lines", "utf-8") - - // This is how File.changed() reads untracked files - const content = await Filesystem.readText(untrackedPath) - const lines = content.split("\n").length - expect(lines).toBe(2) - }, - }) - }) + it.instance( + "reads untracked files via Filesystem.readText()", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const untrackedPath = path.join(test.directory, "untracked.txt") + yield* Effect.promise(() => fs.writeFile(untrackedPath, "new content\nwith multiple lines", "utf-8")) + + const content = yield* Effect.promise(() => Filesystem.readText(untrackedPath)) + expect(content.split("\n").length).toBe(2) + }), + { git: true }, + ) }) describe("Error handling", () => { - test("handles errors gracefully in Filesystem.readText()", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "readonly.txt") - await fs.writeFile(filepath, "content", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nonExistentPath = path.join(tmp.path, "does-not-exist.txt") - // Filesystem.readText() on non-existent file throws - await expect(Filesystem.readText(nonExistentPath)).rejects.toThrow() - - // But read() handles this gracefully - const result = await read("does-not-exist.txt") - expect(result.content).toBe("") - }, - }) - }) - - test("handles errors in Filesystem.readArrayBuffer()", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nonExistentPath = path.join(tmp.path, "does-not-exist.bin") - const buffer = await Filesystem.readArrayBuffer(nonExistentPath).catch(() => new ArrayBuffer(0)) - expect(buffer.byteLength).toBe(0) - }, - }) - }) - - test("returns empty array buffer on error for images", async () => { - await using tmp = await tmpdir() - const _filepath = path.join(tmp.path, "broken.png") - // Don't create the file - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - // read() handles missing images gracefully - const result = await read("broken.png") - expect(result.type).toBe("text") - expect(result.content).toBe("") - }, - }) - }) + it.instance("handles errors gracefully in Filesystem.readText()", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "readonly.txt"), "content", "utf-8")) + + const nonExistentPath = path.join(test.directory, "does-not-exist.txt") + expect(Exit.isFailure(yield* Effect.promise(() => Filesystem.readText(nonExistentPath)).pipe(Effect.exit))).toBe(true) + + const result = yield* read("does-not-exist.txt") + expect(result.content).toBe("") + }), + ) + + it.instance("handles errors in Filesystem.readArrayBuffer()", () => + Effect.gen(function* () { + const test = yield* TestInstance + const nonExistentPath = path.join(test.directory, "does-not-exist.bin") + const buffer = yield* Effect.promise(() => Filesystem.readArrayBuffer(nonExistentPath).catch(() => new ArrayBuffer(0))) + expect(buffer.byteLength).toBe(0) + }), + ) + + it.instance("returns empty array buffer on error for images", () => + Effect.gen(function* () { + const result = yield* read("broken.png") + expect(result.type).toBe("text") + expect(result.content).toBe("") + }), + ) }) describe("shouldEncode() logic", () => { - test("treats .ts files as text", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "test.ts") - await fs.writeFile(filepath, "export const value = 1", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("test.ts") - expect(result.type).toBe("text") - expect(result.content).toBe("export const value = 1") - }, - }) - }) - - test("treats .mts files as text", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "test.mts") - await fs.writeFile(filepath, "export const value = 1", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("test.mts") - expect(result.type).toBe("text") - expect(result.content).toBe("export const value = 1") - }, - }) - }) - - test("treats .sh files as text", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "test.sh") - await fs.writeFile(filepath, "#!/usr/bin/env bash\necho hello", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("test.sh") - expect(result.type).toBe("text") - expect(result.content).toBe("#!/usr/bin/env bash\necho hello") - }, - }) - }) - - test("treats Dockerfile as text", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "Dockerfile") - await fs.writeFile(filepath, "FROM alpine:3.20", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("Dockerfile") - expect(result.type).toBe("text") - expect(result.content).toBe("FROM alpine:3.20") - }, - }) - }) - - test("returns encoding info for text files", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "test.txt") - await fs.writeFile(filepath, "simple text", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("test.txt") - expect(result.encoding).toBeUndefined() - expect(result.type).toBe("text") - }, - }) - }) - - test("returns base64 encoding for images", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "test.jpg") - await fs.writeFile(filepath, Buffer.from([0xff, 0xd8, 0xff, 0xe0]), "binary") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("test.jpg") - expect(result.encoding).toBe("base64") - expect(result.mimeType).toBe("image/jpeg") - }, - }) - }) + it.instance("treats .ts files as text", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.ts"), "export const value = 1", "utf-8")) + + const result = yield* read("test.ts") + expect(result.type).toBe("text") + expect(result.content).toBe("export const value = 1") + }), + ) + + it.instance("treats .mts files as text", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.mts"), "export const value = 1", "utf-8")) + + const result = yield* read("test.mts") + expect(result.type).toBe("text") + expect(result.content).toBe("export const value = 1") + }), + ) + + it.instance("treats .sh files as text", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.sh"), "#!/usr/bin/env bash\necho hello", "utf-8")) + + const result = yield* read("test.sh") + expect(result.type).toBe("text") + expect(result.content).toBe("#!/usr/bin/env bash\necho hello") + }), + ) + + it.instance("treats Dockerfile as text", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "Dockerfile"), "FROM alpine:3.20", "utf-8")) + + const result = yield* read("Dockerfile") + expect(result.type).toBe("text") + expect(result.content).toBe("FROM alpine:3.20") + }), + ) + + it.instance("returns encoding info for text files", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.txt"), "simple text", "utf-8")) + + const result = yield* read("test.txt") + expect(result.encoding).toBeUndefined() + expect(result.type).toBe("text") + }), + ) + + it.instance("returns base64 encoding for images", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.jpg"), Buffer.from([0xff, 0xd8, 0xff, 0xe0]))) + + const result = yield* read("test.jpg") + expect(result.encoding).toBe("base64") + expect(result.mimeType).toBe("image/jpeg") + }), + ) }) describe("Path security", () => { - test("throws for paths outside project directory", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(read("../outside.txt")).rejects.toThrow("Access denied") - }, - }) - }) - - test("throws for paths outside project directory", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(read("../outside.txt")).rejects.toThrow("Access denied") - }, - }) - }) + it.instance("throws for paths outside project directory", () => + Effect.gen(function* () { + expect(yield* failureMessage(read("../outside.txt"))).toContain("Access denied") + }), + ) + + it.instance("throws for paths outside project directory", () => + Effect.gen(function* () { + expect(yield* failureMessage(read("../outside.txt"))).toContain("Access denied") + }), + ) }) describe("status()", () => { - test("detects modified file", async () => { - await using tmp = await tmpdir({ git: true }) - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "original\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit -m "add file"`.cwd(tmp.path).quiet() - await fs.writeFile(filepath, "modified\nextra line\n", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await status() - const entry = result.find((f) => f.path === "file.txt") + it.instance( + "detects modified file", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* Effect.promise(() => fs.writeFile(filepath, "original\n", "utf-8")) + yield* gitAddAll(test.directory) + yield* gitCommit(test.directory, "add file") + yield* Effect.promise(() => fs.writeFile(filepath, "modified\nextra line\n", "utf-8")) + + const result = yield* status() + const entry = result.find((file) => file.path === "file.txt") expect(entry).toBeDefined() expect(entry!.status).toBe("modified") expect(entry!.added).toBeGreaterThan(0) expect(entry!.removed).toBeGreaterThan(0) - }, - }) - }) - - test("detects untracked file as added", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "new.txt"), "line1\nline2\nline3\n", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await status() - const entry = result.find((f) => f.path === "new.txt") + }), + { git: true }, + ) + + it.instance( + "detects untracked file as added", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "new.txt"), "line1\nline2\nline3\n", "utf-8")) + + const result = yield* status() + const entry = result.find((file) => file.path === "new.txt") expect(entry).toBeDefined() expect(entry!.status).toBe("added") - expect(entry!.added).toBe(4) // 3 lines + trailing newline splits to 4 + expect(entry!.added).toBe(4) expect(entry!.removed).toBe(0) - }, - }) - }) - - test("detects deleted file", async () => { - await using tmp = await tmpdir({ git: true }) - const filepath = path.join(tmp.path, "gone.txt") - await fs.writeFile(filepath, "content\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit -m "add file"`.cwd(tmp.path).quiet() - await fs.rm(filepath) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await status() - // Deleted files appear in both numstat (as "modified") and diff-filter=D (as "deleted") - const entries = result.filter((f) => f.path === "gone.txt") - expect(entries.some((e) => e.status === "deleted")).toBe(true) - }, - }) - }) - - test("detects mixed changes", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "keep.txt"), "keep\n", "utf-8") - await fs.writeFile(path.join(tmp.path, "remove.txt"), "remove\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit -m "initial"`.cwd(tmp.path).quiet() - - // Modify one, delete one, add one - await fs.writeFile(path.join(tmp.path, "keep.txt"), "changed\n", "utf-8") - await fs.rm(path.join(tmp.path, "remove.txt")) - await fs.writeFile(path.join(tmp.path, "brand-new.txt"), "hello\n", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await status() - expect(result.some((f) => f.path === "keep.txt" && f.status === "modified")).toBe(true) - expect(result.some((f) => f.path === "remove.txt" && f.status === "deleted")).toBe(true) - expect(result.some((f) => f.path === "brand-new.txt" && f.status === "added")).toBe(true) - }, - }) - }) - - test("returns empty for non-git project", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await status() - expect(result).toEqual([]) - }, - }) - }) - - test("returns empty for clean repo", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await status() - expect(result).toEqual([]) - }, - }) - }) - - test("parses binary numstat as 0", async () => { - await using tmp = await tmpdir({ git: true }) - const filepath = path.join(tmp.path, "data.bin") - // Write content with null bytes so git treats it as binary - const binaryData = Buffer.alloc(256) - for (let i = 0; i < 256; i++) binaryData[i] = i - await fs.writeFile(filepath, binaryData) - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit -m "add binary"`.cwd(tmp.path).quiet() - // Modify the binary - const modified = Buffer.alloc(512) - for (let i = 0; i < 512; i++) modified[i] = i % 256 - await fs.writeFile(filepath, modified) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await status() - const entry = result.find((f) => f.path === "data.bin") + }), + { git: true }, + ) + + it.instance( + "detects deleted file", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "gone.txt") + yield* Effect.promise(() => fs.writeFile(filepath, "content\n", "utf-8")) + yield* gitAddAll(test.directory) + yield* gitCommit(test.directory, "add file") + yield* Effect.promise(() => fs.rm(filepath)) + + const result = yield* status() + const entries = result.filter((file) => file.path === "gone.txt") + expect(entries.some((entry) => entry.status === "deleted")).toBe(true) + }), + { git: true }, + ) + + it.instance( + "detects mixed changes", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "keep.txt"), "keep\n", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "remove.txt"), "remove\n", "utf-8")) + yield* gitAddAll(test.directory) + yield* gitCommit(test.directory, "initial") + + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "keep.txt"), "changed\n", "utf-8")) + yield* Effect.promise(() => fs.rm(path.join(test.directory, "remove.txt"))) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "brand-new.txt"), "hello\n", "utf-8")) + + const result = yield* status() + expect(result.some((file) => file.path === "keep.txt" && file.status === "modified")).toBe(true) + expect(result.some((file) => file.path === "remove.txt" && file.status === "deleted")).toBe(true) + expect(result.some((file) => file.path === "brand-new.txt" && file.status === "added")).toBe(true) + }), + { git: true }, + ) + + it.instance("returns empty for non-git project", () => + Effect.gen(function* () { + expect(yield* status()).toEqual([]) + }), + ) + + it.instance( + "returns empty for clean repo", + () => + Effect.gen(function* () { + expect(yield* status()).toEqual([]) + }), + { git: true }, + ) + + it.instance( + "parses binary numstat as 0", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "data.bin") + yield* Effect.promise(() => fs.writeFile(filepath, Buffer.from(Array.from({ length: 256 }, (_, index) => index)))) + yield* gitAddAll(test.directory) + yield* gitCommit(test.directory, "add binary") + yield* Effect.promise(() => fs.writeFile(filepath, Buffer.from(Array.from({ length: 512 }, (_, index) => index % 256)))) + + const result = yield* status() + const entry = result.find((file) => file.path === "data.bin") expect(entry).toBeDefined() expect(entry!.status).toBe("modified") expect(entry!.added).toBe(0) expect(entry!.removed).toBe(0) - }, - }) - }) + }), + { git: true }, + ) }) describe("list()", () => { - test("returns files and directories with correct shape", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.mkdir(path.join(tmp.path, "subdir")) - await fs.writeFile(path.join(tmp.path, "file.txt"), "content", "utf-8") - await fs.writeFile(path.join(tmp.path, "subdir", "nested.txt"), "nested", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nodes = await list() + it.instance( + "returns files and directories with correct shape", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.mkdir(path.join(test.directory, "subdir"))) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "file.txt"), "content", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "subdir", "nested.txt"), "nested", "utf-8")) + + const nodes = yield* list() expect(nodes.length).toBeGreaterThanOrEqual(2) for (const node of nodes) { expect(node).toHaveProperty("name") @@ -561,289 +493,260 @@ describe("file/index Filesystem patterns", () => { expect(node).toHaveProperty("ignored") expect(["file", "directory"]).toContain(node.type) } - }, - }) - }) - - test("sorts directories before files, alphabetical within each", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.mkdir(path.join(tmp.path, "beta")) - await fs.mkdir(path.join(tmp.path, "alpha")) - await fs.writeFile(path.join(tmp.path, "zz.txt"), "", "utf-8") - await fs.writeFile(path.join(tmp.path, "aa.txt"), "", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nodes = await list() - const dirs = nodes.filter((n) => n.type === "directory") - const files = nodes.filter((n) => n.type === "file") - // Dirs come first - const firstFile = nodes.findIndex((n) => n.type === "file") - const lastDir = nodes.findLastIndex((n) => n.type === "directory") + }), + { git: true }, + ) + + it.instance( + "sorts directories before files, alphabetical within each", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.mkdir(path.join(test.directory, "beta"))) + yield* Effect.promise(() => fs.mkdir(path.join(test.directory, "alpha"))) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "zz.txt"), "", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "aa.txt"), "", "utf-8")) + + const nodes = yield* list() + const dirs = nodes.filter((node) => node.type === "directory") + const files = nodes.filter((node) => node.type === "file") + const firstFile = nodes.findIndex((node) => node.type === "file") + const lastDir = nodes.findLastIndex((node) => node.type === "directory") if (lastDir >= 0 && firstFile >= 0) { expect(lastDir).toBeLessThan(firstFile) } - // Alphabetical within dirs - expect(dirs.map((d) => d.name)).toEqual(dirs.map((d) => d.name).toSorted()) - // Alphabetical within files - expect(files.map((f) => f.name)).toEqual(files.map((f) => f.name).toSorted()) - }, - }) - }) - - test("excludes .git and .DS_Store", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, ".DS_Store"), "", "utf-8") - await fs.writeFile(path.join(tmp.path, "visible.txt"), "", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nodes = await list() - const names = nodes.map((n) => n.name) + expect(dirs.map((dir) => dir.name)).toEqual(dirs.map((dir) => dir.name).toSorted()) + expect(files.map((file) => file.name)).toEqual(files.map((file) => file.name).toSorted()) + }), + { git: true }, + ) + + it.instance( + "excludes .git and .DS_Store", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, ".DS_Store"), "", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "visible.txt"), "", "utf-8")) + + const names = (yield* list()).map((node) => node.name) expect(names).not.toContain(".git") expect(names).not.toContain(".DS_Store") expect(names).toContain("visible.txt") - }, - }) - }) - - test("marks gitignored files as ignored", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, ".gitignore"), "*.log\nbuild/\n", "utf-8") - await fs.writeFile(path.join(tmp.path, "app.log"), "log data", "utf-8") - await fs.writeFile(path.join(tmp.path, "main.ts"), "code", "utf-8") - await fs.mkdir(path.join(tmp.path, "build")) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nodes = await list() - const logNode = nodes.find((n) => n.name === "app.log") - const tsNode = nodes.find((n) => n.name === "main.ts") - const buildNode = nodes.find((n) => n.name === "build") - expect(logNode?.ignored).toBe(true) - expect(tsNode?.ignored).toBe(false) - expect(buildNode?.ignored).toBe(true) - }, - }) - }) - - test("lists subdirectory contents", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.mkdir(path.join(tmp.path, "sub")) - await fs.writeFile(path.join(tmp.path, "sub", "a.txt"), "", "utf-8") - await fs.writeFile(path.join(tmp.path, "sub", "b.txt"), "", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nodes = await list("sub") + }), + { git: true }, + ) + + it.instance( + "marks gitignored files as ignored", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, ".gitignore"), "*.log\nbuild/\n", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "app.log"), "log data", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "main.ts"), "code", "utf-8")) + yield* Effect.promise(() => fs.mkdir(path.join(test.directory, "build"))) + + const nodes = yield* list() + expect(nodes.find((node) => node.name === "app.log")?.ignored).toBe(true) + expect(nodes.find((node) => node.name === "main.ts")?.ignored).toBe(false) + expect(nodes.find((node) => node.name === "build")?.ignored).toBe(true) + }), + { git: true }, + ) + + it.instance( + "lists subdirectory contents", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.mkdir(path.join(test.directory, "sub"))) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "sub", "a.txt"), "", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "sub", "b.txt"), "", "utf-8")) + + const nodes = yield* list("sub") expect(nodes.length).toBe(2) - expect(nodes.map((n) => n.name).sort()).toEqual(["a.txt", "b.txt"]) - // Paths should be relative to project root (normalize for Windows) + expect(nodes.map((node) => node.name).sort()).toEqual(["a.txt", "b.txt"]) expect(nodes[0].path.replaceAll("\\", "/").startsWith("sub/")).toBe(true) - }, - }) - }) - - test("throws for paths outside project directory", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(list("../outside")).rejects.toThrow("Access denied") - }, - }) - }) - - test("works without git", async () => { - await using tmp = await tmpdir() - await fs.writeFile(path.join(tmp.path, "file.txt"), "hi", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nodes = await list() - expect(nodes.length).toBeGreaterThanOrEqual(1) - // Without git, ignored should be false for all - for (const node of nodes) { - expect(node.ignored).toBe(false) - } - }, - }) - }) + }), + { git: true }, + ) + + it.instance( + "throws for paths outside project directory", + () => + Effect.gen(function* () { + expect(yield* failureMessage(list("../outside"))).toContain("Access denied") + }), + { git: true }, + ) + + it.instance("works without git", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "file.txt"), "hi", "utf-8")) + + const nodes = yield* list() + expect(nodes.length).toBeGreaterThanOrEqual(1) + for (const node of nodes) { + expect(node.ignored).toBe(false) + } + }), + ) }) describe("search()", () => { - async function setupSearchableRepo() { - const tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "index.ts"), "code", "utf-8") - await fs.writeFile(path.join(tmp.path, "utils.ts"), "utils", "utf-8") - await fs.writeFile(path.join(tmp.path, "readme.md"), "readme", "utf-8") - await fs.mkdir(path.join(tmp.path, "src")) - await fs.mkdir(path.join(tmp.path, ".hidden")) - await fs.writeFile(path.join(tmp.path, "src", "main.ts"), "main", "utf-8") - await fs.writeFile(path.join(tmp.path, ".hidden", "secret.ts"), "secret", "utf-8") - return tmp - } - - test("empty query returns files", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - - const result = await search({ query: "", type: "file" }) + it.instance( + "empty query returns files", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + yield* init() + + const result = yield* search({ query: "", type: "file" }) expect(result.length).toBeGreaterThan(0) - }, - }) - }) - - test("search works before explicit init", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await search({ query: "main", type: "file" }) - expect(result.some((f) => f.includes("main"))).toBe(true) - }, - }) - }) - - test("empty query returns dirs sorted with hidden last", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - - const result = await search({ query: "", type: "directory" }) + }), + { git: true }, + ) + + it.instance( + "search works before explicit init", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + + const result = yield* search({ query: "main", type: "file" }) + expect(result.some((file) => file.includes("main"))).toBe(true) + }), + { git: true }, + ) + + it.instance( + "empty query returns dirs sorted with hidden last", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + yield* init() + + const result = yield* search({ query: "", type: "directory" }) expect(result.length).toBeGreaterThan(0) - // Find first hidden dir index - const firstHidden = result.findIndex((d) => d.split("/").some((p) => p.startsWith(".") && p.length > 1)) - const lastVisible = result.findLastIndex((d) => !d.split("/").some((p) => p.startsWith(".") && p.length > 1)) + const firstHidden = result.findIndex((dir) => dir.split("/").some((part) => part.startsWith(".") && part.length > 1)) + const lastVisible = result.findLastIndex((dir) => !dir.split("/").some((part) => part.startsWith(".") && part.length > 1)) if (firstHidden >= 0 && lastVisible >= 0) { expect(firstHidden).toBeGreaterThan(lastVisible) } - }, - }) - }) - - test("fuzzy matches file names", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - - const result = await search({ query: "main", type: "file" }) - expect(result.some((f) => f.includes("main"))).toBe(true) - }, - }) - }) - - test("type filter returns only files", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - - const result = await search({ query: "", type: "file" }) - // Files don't end with / - for (const f of result) { - expect(f.endsWith("/")).toBe(false) + }), + { git: true }, + ) + + it.instance( + "fuzzy matches file names", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + yield* init() + + const result = yield* search({ query: "main", type: "file" }) + expect(result.some((file) => file.includes("main"))).toBe(true) + }), + { git: true }, + ) + + it.instance( + "type filter returns only files", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + yield* init() + + const result = yield* search({ query: "", type: "file" }) + for (const file of result) { + expect(file.endsWith("/")).toBe(false) } - }, - }) - }) - - test("type filter returns only directories", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - - const result = await search({ query: "", type: "directory" }) - // Directories end with / - for (const d of result) { - expect(d.endsWith("/")).toBe(true) + }), + { git: true }, + ) + + it.instance( + "type filter returns only directories", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + yield* init() + + const result = yield* search({ query: "", type: "directory" }) + for (const dir of result) { + expect(dir.endsWith("/")).toBe(true) } - }, - }) - }) - - test("respects limit", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - - const result = await search({ query: "", type: "file", limit: 2 }) + }), + { git: true }, + ) + + it.instance( + "respects limit", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + yield* init() + + const result = yield* search({ query: "", type: "file", limit: 2 }) expect(result.length).toBeLessThanOrEqual(2) - }, - }) - }) - - test("query starting with dot prefers hidden files", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - - const result = await search({ query: ".hidden", type: "directory" }) + }), + { git: true }, + ) + + it.instance( + "query starting with dot prefers hidden files", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + yield* init() + + const result = yield* search({ query: ".hidden", type: "directory" }) expect(result.length).toBeGreaterThan(0) expect(result[0]).toContain(".hidden") - }, - }) - }) - - test("search refreshes after init when files change", async () => { - await using tmp = await setupSearchableRepo() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - expect(await search({ query: "fresh", type: "file" })).toEqual([]) - - await fs.writeFile(path.join(tmp.path, "fresh.ts"), "fresh", "utf-8") - - const result = await search({ query: "fresh", type: "file" }) - expect(result).toContain("fresh.ts") - }, - }) - }) + }), + { git: true }, + ) + + it.instance( + "search refreshes after init when files change", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* setupSearchableRepo(test.directory) + yield* init() + expect(yield* search({ query: "fresh", type: "file" })).toEqual([]) + + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "fresh.ts"), "fresh", "utf-8")) + + expect(yield* search({ query: "fresh", type: "file" })).toContain("fresh.ts") + }), + { git: true }, + ) }) describe("read() - diff/patch", () => { - test("returns diff and patch for modified tracked file", async () => { - await using tmp = await tmpdir({ git: true }) - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "original content\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit -m "add file"`.cwd(tmp.path).quiet() - await fs.writeFile(filepath, "modified content\n", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("file.txt") + it.instance( + "returns diff and patch for modified tracked file", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* Effect.promise(() => fs.writeFile(filepath, "original content\n", "utf-8")) + yield* gitAddAll(test.directory) + yield* gitCommit(test.directory, "add file") + yield* Effect.promise(() => fs.writeFile(filepath, "modified content\n", "utf-8")) + + const result = yield* read("file.txt") expect(result.type).toBe("text") expect(result.content).toBe("modified content") expect(result.diff).toBeDefined() @@ -851,107 +754,90 @@ describe("file/index Filesystem patterns", () => { expect(result.diff).toContain("modified content") expect(result.patch).toBeDefined() expect(result.patch!.hunks.length).toBeGreaterThan(0) - }, - }) - }) - - test("returns diff for staged changes", async () => { - await using tmp = await tmpdir({ git: true }) - const filepath = path.join(tmp.path, "staged.txt") - await fs.writeFile(filepath, "before\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit -m "add file"`.cwd(tmp.path).quiet() - await fs.writeFile(filepath, "after\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("staged.txt") + }), + { git: true }, + ) + + it.instance( + "returns diff for staged changes", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "staged.txt") + yield* Effect.promise(() => fs.writeFile(filepath, "before\n", "utf-8")) + yield* gitAddAll(test.directory) + yield* gitCommit(test.directory, "add file") + yield* Effect.promise(() => fs.writeFile(filepath, "after\n", "utf-8")) + yield* gitAddAll(test.directory) + + const result = yield* read("staged.txt") expect(result.diff).toBeDefined() expect(result.patch).toBeDefined() - }, - }) - }) - - test("returns no diff for unmodified file", async () => { - await using tmp = await tmpdir({ git: true }) - const filepath = path.join(tmp.path, "clean.txt") - await fs.writeFile(filepath, "unchanged\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit -m "add file"`.cwd(tmp.path).quiet() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("clean.txt") + }), + { git: true }, + ) + + it.instance( + "returns no diff for unmodified file", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "clean.txt") + yield* Effect.promise(() => fs.writeFile(filepath, "unchanged\n", "utf-8")) + yield* gitAddAll(test.directory) + yield* gitCommit(test.directory, "add file") + + const result = yield* read("clean.txt") expect(result.type).toBe("text") expect(result.content).toBe("unchanged") expect(result.diff).toBeUndefined() expect(result.patch).toBeUndefined() - }, - }) - }) + }), + { git: true }, + ) }) describe("InstanceState isolation", () => { - test("two directories get independent file caches", async () => { - await using one = await tmpdir({ git: true }) - await using two = await tmpdir({ git: true }) - await fs.writeFile(path.join(one.path, "a.ts"), "one", "utf-8") - await fs.writeFile(path.join(two.path, "b.ts"), "two", "utf-8") - - await WithInstance.provide({ - directory: one.path, - fn: async () => { - await init() - const results = await search({ query: "a.ts", type: "file" }) - expect(results).toContain("a.ts") - const results2 = await search({ query: "b.ts", type: "file" }) - expect(results2).not.toContain("b.ts") - }, - }) - - await WithInstance.provide({ - directory: two.path, - fn: async () => { - await init() - const results = await search({ query: "b.ts", type: "file" }) - expect(results).toContain("b.ts") - const results2 = await search({ query: "a.ts", type: "file" }) - expect(results2).not.toContain("a.ts") - }, - }) - }) - - test("disposal gives fresh state on next access", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "before.ts"), "before", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - const results = await search({ query: "before", type: "file" }) - expect(results).toContain("before.ts") - }, - }) - - await disposeAllInstances() - - await fs.writeFile(path.join(tmp.path, "after.ts"), "after", "utf-8") - await fs.rm(path.join(tmp.path, "before.ts")) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await init() - const results = await search({ query: "after", type: "file" }) - expect(results).toContain("after.ts") - const stale = await search({ query: "before", type: "file" }) - expect(stale).not.toContain("before.ts") - }, - }) - }) + it.instance( + "two directories get independent file caches", + () => + Effect.gen(function* () { + const one = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(one.directory, "a.ts"), "one", "utf-8")) + yield* init() + expect(yield* search({ query: "a.ts", type: "file" })).toContain("a.ts") + expect(yield* search({ query: "b.ts", type: "file" })).not.toContain("b.ts") + + yield* Effect.gen(function* () { + const two = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(two.directory, "b.ts"), "two", "utf-8")) + yield* init() + expect(yield* search({ query: "b.ts", type: "file" })).toContain("b.ts") + expect(yield* search({ query: "a.ts", type: "file" })).not.toContain("a.ts") + }).pipe(withTmpdirInstance({ git: true })) + }), + { git: true }, + ) + + it.instance( + "disposal gives fresh state on next access", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "before.ts"), "before", "utf-8")) + yield* init() + expect(yield* search({ query: "before", type: "file" })).toContain("before.ts") + + yield* Effect.promise(() => disposeAllInstances()) + + yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "after.ts"), "after", "utf-8")) + yield* Effect.promise(() => fs.rm(path.join(test.directory, "before.ts"))) + + yield* init() + expect(yield* search({ query: "after", type: "file" })).toContain("after.ts") + expect(yield* search({ query: "before", type: "file" })).not.toContain("before.ts") + }), + { git: true }, + ) }) }) From 44edb639c2c2b2874e7cdeb25e048ccad8be449c Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 20:40:23 -0400 Subject: [PATCH 059/378] test(session): migrate message pagination to Effect runner (#26957) --- .../test/session/messages-pagination.test.ts | 1806 +++++++---------- 1 file changed, 782 insertions(+), 1024 deletions(-) diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index 86e1d85d0..49828a9b6 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -1,46 +1,41 @@ import { describe, expect, test } from "bun:test" import { Effect } from "effect" -import path from "path" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { Session as SessionNs } from "@/session/session" import { MessageV2 } from "../../src/session/message-v2" import { MessageID, PartID, type SessionID } from "../../src/session/schema" import { ModelID, ProviderID } from "../../src/provider/schema" import * as Log from "@opencode-ai/core/util/log" +import { testEffect } from "../lib/effect" -const root = path.join(__dirname, "../..") void Log.init({ print: false }) -function run(fx: Effect.Effect) { - return Effect.runPromise(fx.pipe(Effect.provide(SessionNs.defaultLayer))) -} - -const svc = { - ...SessionNs, - create(input?: SessionNs.CreateInput) { - return run(SessionNs.Service.use((svc) => svc.create(input))) - }, - remove(id: SessionID) { - return run(SessionNs.Service.use((svc) => svc.remove(id))) - }, - updateMessage(msg: T) { - return run(SessionNs.Service.use((svc) => svc.updateMessage(msg))) - }, - updatePart(part: T) { - return run(SessionNs.Service.use((svc) => svc.updatePart(part))) - }, - fork(input: { sessionID: SessionID; messageID?: MessageID }) { - return run(SessionNs.Service.use((svc) => svc.fork(input))) - }, -} - -async function fill(sessionID: SessionID, count: number, time = (i: number) => Date.now() + i) { +const it = testEffect(SessionNs.defaultLayer) + +const withSession = ( + fn: (input: { session: SessionNs.Interface; sessionID: SessionID }) => Effect.Effect, +) => + Effect.acquireUseRelease( + Effect.gen(function* () { + const session = yield* SessionNs.Service + const created = yield* session.create({}) + return { session, sessionID: created.id } + }), + fn, + (input) => input.session.remove(input.sessionID).pipe(Effect.ignore), + ) + +// Helper functions using Effect.gen +const fill = Effect.fn("Test.fill")(function* ( + sessionID: SessionID, + count: number, + time = (i: number) => Date.now() + i, +) { + const session = yield* SessionNs.Service const ids = [] as MessageID[] for (let i = 0; i < count; i++) { const id = MessageID.ascending() ids.push(id) - await svc.updateMessage({ + yield* session.updateMessage({ id, sessionID, role: "user", @@ -50,7 +45,7 @@ async function fill(sessionID: SessionID, count: number, time = (i: number) => D tools: {}, mode: "", } as unknown as MessageV2.Info) - await svc.updatePart({ + yield* session.updatePart({ id: PartID.ascending(), sessionID, messageID: id, @@ -59,11 +54,12 @@ async function fill(sessionID: SessionID, count: number, time = (i: number) => D }) } return ids -} +}) -async function addUser(sessionID: SessionID, text?: string) { +const addUser = Effect.fn("Test.addUser")(function* (sessionID: SessionID, text?: string) { + const session = yield* SessionNs.Service const id = MessageID.ascending() - await svc.updateMessage({ + yield* session.updateMessage({ id, sessionID, role: "user", @@ -74,7 +70,7 @@ async function addUser(sessionID: SessionID, text?: string) { mode: "", } as unknown as MessageV2.Info) if (text) { - await svc.updatePart({ + yield* session.updatePart({ id: PartID.ascending(), sessionID, messageID: id, @@ -83,15 +79,16 @@ async function addUser(sessionID: SessionID, text?: string) { }) } return id -} +}) -async function addAssistant( +const addAssistant = Effect.fn("Test.addAssistant")(function* ( sessionID: SessionID, parentID: MessageID, opts?: { summary?: boolean; finish?: string; error?: MessageV2.Assistant["error"] }, ) { + const session = yield* SessionNs.Service const id = MessageID.ascending() - await svc.updateMessage({ + yield* session.updateMessage({ id, sessionID, role: "assistant", @@ -109,10 +106,15 @@ async function addAssistant( error: opts?.error, } as unknown as MessageV2.Info) return id -} +}) -async function addCompactionPart(sessionID: SessionID, messageID: MessageID, tailStartID?: MessageID) { - await svc.updatePart({ +const addCompactionPart = Effect.fn("Test.addCompactionPart")(function* ( + sessionID: SessionID, + messageID: MessageID, + tailStartID?: MessageID, +) { + const session = yield* SessionNs.Service + yield* session.updatePart({ id: PartID.ascending(), sessionID, messageID, @@ -120,933 +122,713 @@ async function addCompactionPart(sessionID: SessionID, messageID: MessageID, tai auto: true, tail_start_id: tailStartID, } as any) -} +}) describe("MessageV2.page", () => { - test("returns sync result", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - await fill(session.id, 2) - - const result = MessageV2.page({ sessionID: session.id, limit: 10 }) - expect(result).toBeDefined() - expect(result.items).toBeArray() - - await svc.remove(session.id) - }, - }) - }) - - test("pages backward with opaque cursors", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 6) - - const a = MessageV2.page({ sessionID: session.id, limit: 2 }) - expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) - expect(a.items.every((item) => item.parts.length === 1)).toBe(true) - expect(a.more).toBe(true) - expect(a.cursor).toBeTruthy() - - const b = MessageV2.page({ sessionID: session.id, limit: 2, before: a.cursor! }) - expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) - expect(b.more).toBe(true) - expect(b.cursor).toBeTruthy() - - const c = MessageV2.page({ sessionID: session.id, limit: 2, before: b.cursor! }) - expect(c.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) - expect(c.more).toBe(false) - expect(c.cursor).toBeUndefined() - - await svc.remove(session.id) - }, - }) - }) - - test("returns items in chronological order within a page", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 4) - - const result = MessageV2.page({ sessionID: session.id, limit: 4 }) - expect(result.items.map((item) => item.info.id)).toEqual(ids) - - await svc.remove(session.id) - }, - }) - }) - - test("returns empty items for session with no messages", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const result = MessageV2.page({ sessionID: session.id, limit: 10 }) - expect(result.items).toEqual([]) - expect(result.more).toBe(false) - expect(result.cursor).toBeUndefined() - - await svc.remove(session.id) - }, - }) - }) - - test("throws NotFoundError for non-existent session", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const fake = "non-existent-session" as SessionID - expect(() => MessageV2.page({ sessionID: fake, limit: 10 })).toThrow("NotFoundError") - }, - }) - }) - - test("handles exact limit boundary", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 3) - - const result = MessageV2.page({ sessionID: session.id, limit: 3 }) - expect(result.items.map((item) => item.info.id)).toEqual(ids) - expect(result.more).toBe(false) - expect(result.cursor).toBeUndefined() - - await svc.remove(session.id) - }, - }) - }) - - test("limit of 1 returns single newest message", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 5) - - const result = MessageV2.page({ sessionID: session.id, limit: 1 }) - expect(result.items).toHaveLength(1) - expect(result.items[0].info.id).toBe(ids[ids.length - 1]) - expect(result.more).toBe(true) - - await svc.remove(session.id) - }, - }) - }) - - test("hydrates multiple parts per message", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const [id] = await fill(session.id, 1) - - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: id, - type: "text", - text: "extra", - }) - - const result = MessageV2.page({ sessionID: session.id, limit: 10 }) - expect(result.items).toHaveLength(1) - expect(result.items[0].parts).toHaveLength(2) - - await svc.remove(session.id) - }, - }) - }) - - test("accepts cursors from fractional timestamps", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 4, (i) => 1000.5 + i) - - const a = MessageV2.page({ sessionID: session.id, limit: 2 }) - const b = MessageV2.page({ sessionID: session.id, limit: 2, before: a.cursor! }) - - expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) - expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) - - await svc.remove(session.id) - }, - }) - }) - - test("messages with same timestamp are ordered by id", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 4, () => 1000) - - const a = MessageV2.page({ sessionID: session.id, limit: 2 }) - expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) - expect(a.more).toBe(true) - - const b = MessageV2.page({ sessionID: session.id, limit: 2, before: a.cursor! }) - expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) - expect(b.more).toBe(false) - - await svc.remove(session.id) - }, - }) - }) - - test("does not return messages from other sessions", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const a = await svc.create({}) - const b = await svc.create({}) - await fill(a.id, 3) - await fill(b.id, 2) - - const resultA = MessageV2.page({ sessionID: a.id, limit: 10 }) - const resultB = MessageV2.page({ sessionID: b.id, limit: 10 }) - expect(resultA.items).toHaveLength(3) - expect(resultB.items).toHaveLength(2) - expect(resultA.items.every((item) => item.info.sessionID === a.id)).toBe(true) - expect(resultB.items.every((item) => item.info.sessionID === b.id)).toBe(true) - - await svc.remove(a.id) - await svc.remove(b.id) - }, - }) - }) - - test("large limit returns all messages without cursor", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 10) - - const result = MessageV2.page({ sessionID: session.id, limit: 100 }) - expect(result.items).toHaveLength(10) - expect(result.items.map((item) => item.info.id)).toEqual(ids) - expect(result.more).toBe(false) - expect(result.cursor).toBeUndefined() - - await svc.remove(session.id) - }, - }) - }) + it.instance("returns sync result", () => + withSession(({ sessionID }) => Effect.gen(function* () { + yield* fill(sessionID, 2) + + const result = MessageV2.page({ sessionID, limit: 10 }) + expect(result).toBeDefined() + expect(result.items).toBeArray() + })), + ) + + it.instance("pages backward with opaque cursors", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 6) + + const a = MessageV2.page({ sessionID, limit: 2 }) + expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) + expect(a.items.every((item) => item.parts.length === 1)).toBe(true) + expect(a.more).toBe(true) + expect(a.cursor).toBeTruthy() + + const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) + expect(b.more).toBe(true) + expect(b.cursor).toBeTruthy() + + const c = MessageV2.page({ sessionID, limit: 2, before: b.cursor! }) + expect(c.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) + expect(c.more).toBe(false) + expect(c.cursor).toBeUndefined() + })), + ) + + it.instance("returns items in chronological order within a page", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 4) + + const result = MessageV2.page({ sessionID, limit: 4 }) + expect(result.items.map((item) => item.info.id)).toEqual(ids) + })), + ) + + it.instance("returns empty items for session with no messages", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const result = MessageV2.page({ sessionID, limit: 10 }) + expect(result.items).toEqual([]) + expect(result.more).toBe(false) + expect(result.cursor).toBeUndefined() + })), + ) + + it.instance("throws NotFoundError for non-existent session", () => + Effect.gen(function* () { + const fake = "non-existent-session" as SessionID + expect(() => MessageV2.page({ sessionID: fake, limit: 10 })).toThrow("NotFoundError") + }), + ) + + it.instance("handles exact limit boundary", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 3) + + const result = MessageV2.page({ sessionID, limit: 3 }) + expect(result.items.map((item) => item.info.id)).toEqual(ids) + expect(result.more).toBe(false) + expect(result.cursor).toBeUndefined() + })), + ) + + it.instance("limit of 1 returns single newest message", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 5) + + const result = MessageV2.page({ sessionID, limit: 1 }) + expect(result.items).toHaveLength(1) + expect(result.items[0].info.id).toBe(ids[ids.length - 1]) + expect(result.more).toBe(true) + })), + ) + + it.instance("hydrates multiple parts per message", () => + withSession(({ session, sessionID }) => Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) + + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: "extra", + }) + + const result = MessageV2.page({ sessionID, limit: 10 }) + expect(result.items).toHaveLength(1) + expect(result.items[0].parts).toHaveLength(2) + })), + ) + + it.instance("accepts cursors from fractional timestamps", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 4, (i: number) => 1000.5 + i) + + const a = MessageV2.page({ sessionID, limit: 2 }) + const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + + expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) + expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) + })), + ) + + it.instance("messages with same timestamp are ordered by id", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 4, () => 1000) + + const a = MessageV2.page({ sessionID, limit: 2 }) + expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) + expect(a.more).toBe(true) + + const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) + expect(b.more).toBe(false) + })), + ) + + it.instance("does not return messages from other sessions", () => + Effect.gen(function* () { + const session = yield* SessionNs.Service + const a = yield* session.create({}) + const b = yield* session.create({}) + yield* fill(a.id, 3) + yield* fill(b.id, 2) + + const resultA = MessageV2.page({ sessionID: a.id, limit: 10 }) + const resultB = MessageV2.page({ sessionID: b.id, limit: 10 }) + expect(resultA.items).toHaveLength(3) + expect(resultB.items).toHaveLength(2) + expect(resultA.items.every((item) => item.info.sessionID === a.id)).toBe(true) + expect(resultB.items.every((item) => item.info.sessionID === b.id)).toBe(true) + + yield* session.remove(a.id) + yield* session.remove(b.id) + }), + ) + + it.instance("large limit returns all messages without cursor", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 10) + + const result = MessageV2.page({ sessionID, limit: 100 }) + expect(result.items).toHaveLength(10) + expect(result.items.map((item) => item.info.id)).toEqual(ids) + expect(result.more).toBe(false) + expect(result.cursor).toBeUndefined() + })), + ) }) describe("MessageV2.stream", () => { - test("yields items newest first", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 5) - - const items = Array.from(MessageV2.stream(session.id)) - expect(items.map((item) => item.info.id)).toEqual(ids.slice().reverse()) - - await svc.remove(session.id) - }, - }) - }) - - test("yields nothing for empty session", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const items = Array.from(MessageV2.stream(session.id)) - expect(items).toHaveLength(0) - - await svc.remove(session.id) - }, - }) - }) - - test("yields single message", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 1) - - const items = Array.from(MessageV2.stream(session.id)) - expect(items).toHaveLength(1) - expect(items[0].info.id).toBe(ids[0]) - - await svc.remove(session.id) - }, - }) - }) - - test("hydrates parts for each yielded message", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - await fill(session.id, 3) - - const items = Array.from(MessageV2.stream(session.id)) - for (const item of items) { - expect(item.parts).toHaveLength(1) - expect(item.parts[0].type).toBe("text") - } - - await svc.remove(session.id) - }, - }) - }) - - test("handles sets exceeding internal page size", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 60) - - const items = Array.from(MessageV2.stream(session.id)) - expect(items).toHaveLength(60) - expect(items[0].info.id).toBe(ids[ids.length - 1]) - expect(items[59].info.id).toBe(ids[0]) - - await svc.remove(session.id) - }, - }) - }) - - test("is a sync generator", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - await fill(session.id, 1) - - const gen = MessageV2.stream(session.id) - const first = gen.next() - // sync generator returns { value, done } directly, not a Promise - expect(first).toHaveProperty("value") - expect(first).toHaveProperty("done") - expect(first.done).toBe(false) - - await svc.remove(session.id) - }, - }) - }) + it.instance("yields items newest first", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 5) + + const items = Array.from(MessageV2.stream(sessionID)) + expect(items.map((item) => item.info.id)).toEqual(ids.slice().reverse()) + })), + ) + + it.instance("yields nothing for empty session", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const items = Array.from(MessageV2.stream(sessionID)) + expect(items).toHaveLength(0) + })), + ) + + it.instance("yields single message", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 1) + + const items = Array.from(MessageV2.stream(sessionID)) + expect(items).toHaveLength(1) + expect(items[0].info.id).toBe(ids[0]) + })), + ) + + it.instance("hydrates parts for each yielded message", () => + withSession(({ sessionID }) => Effect.gen(function* () { + yield* fill(sessionID, 3) + + const items = Array.from(MessageV2.stream(sessionID)) + for (const item of items) { + expect(item.parts).toHaveLength(1) + expect(item.parts[0].type).toBe("text") + } + })), + ) + + it.instance("handles sets exceeding internal page size", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 60) + + const items = Array.from(MessageV2.stream(sessionID)) + expect(items).toHaveLength(60) + expect(items[0].info.id).toBe(ids[ids.length - 1]) + expect(items[59].info.id).toBe(ids[0]) + })), + ) + + it.instance("is a sync generator", () => + withSession(({ sessionID }) => Effect.gen(function* () { + yield* fill(sessionID, 1) + + const gen = MessageV2.stream(sessionID) + const first = gen.next() + // sync generator returns { value, done } directly, not a Promise + expect(first).toHaveProperty("value") + expect(first).toHaveProperty("done") + expect(first.done).toBe(false) + })), + ) }) describe("MessageV2.parts", () => { - test("returns parts for a message", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const [id] = await fill(session.id, 1) - - const result = MessageV2.parts(id) - expect(result).toHaveLength(1) - expect(result[0].type).toBe("text") - expect((result[0] as MessageV2.TextPart).text).toBe("m0") - - await svc.remove(session.id) - }, - }) - }) - - test("returns empty array for message with no parts", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const id = await addUser(session.id) - - const result = MessageV2.parts(id) - expect(result).toEqual([]) - - await svc.remove(session.id) - }, - }) - }) - - test("returns multiple parts in order", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const [id] = await fill(session.id, 1) - - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: id, - type: "text", - text: "second", - }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: id, - type: "text", - text: "third", - }) - - const result = MessageV2.parts(id) - expect(result).toHaveLength(3) - expect((result[0] as MessageV2.TextPart).text).toBe("m0") - expect((result[1] as MessageV2.TextPart).text).toBe("second") - expect((result[2] as MessageV2.TextPart).text).toBe("third") - - await svc.remove(session.id) - }, - }) - }) - - test("returns empty for non-existent message id", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - await svc.create({}) - const result = MessageV2.parts(MessageID.ascending()) - expect(result).toEqual([]) - }, - }) - }) - - test("parts contain sessionID and messageID", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const [id] = await fill(session.id, 1) - - const result = MessageV2.parts(id) - expect(result[0].sessionID).toBe(session.id) - expect(result[0].messageID).toBe(id) - - await svc.remove(session.id) - }, - }) - }) + it.instance("returns parts for a message", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) + + const result = MessageV2.parts(id) + expect(result).toHaveLength(1) + expect(result[0].type).toBe("text") + expect((result[0] as MessageV2.TextPart).text).toBe("m0") + })), + ) + + it.instance("returns empty array for message with no parts", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const id = yield* addUser(sessionID) + + const result = MessageV2.parts(id) + expect(result).toEqual([]) + })), + ) + + it.instance("returns multiple parts in order", () => + withSession(({ session, sessionID }) => Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) + + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: "second", + }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: "third", + }) + + const result = MessageV2.parts(id) + expect(result).toHaveLength(3) + expect((result[0] as MessageV2.TextPart).text).toBe("m0") + expect((result[1] as MessageV2.TextPart).text).toBe("second") + expect((result[2] as MessageV2.TextPart).text).toBe("third") + })), + ) + + it.instance("returns empty for non-existent message id", () => + Effect.gen(function* () { + yield* SessionNs.Service + const result = MessageV2.parts(MessageID.ascending()) + expect(result).toEqual([]) + }), + ) + + it.instance("parts contain sessionID and messageID", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) + + const result = MessageV2.parts(id) + expect(result[0].sessionID).toBe(sessionID) + expect(result[0].messageID).toBe(id) + })), + ) }) describe("MessageV2.get", () => { - test("returns message with hydrated parts", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const [id] = await fill(session.id, 1) - - const result = MessageV2.get({ sessionID: session.id, messageID: id }) - expect(result.info.id).toBe(id) - expect(result.info.sessionID).toBe(session.id) - expect(result.info.role).toBe("user") - expect(result.parts).toHaveLength(1) - expect((result.parts[0] as MessageV2.TextPart).text).toBe("m0") - - await svc.remove(session.id) - }, - }) - }) - - test("throws NotFoundError for non-existent message", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - expect(() => MessageV2.get({ sessionID: session.id, messageID: MessageID.ascending() })).toThrow( - "NotFoundError", - ) - - await svc.remove(session.id) - }, - }) - }) - - test("scopes by session id", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const a = await svc.create({}) - const b = await svc.create({}) - const [id] = await fill(a.id, 1) - - expect(() => MessageV2.get({ sessionID: b.id, messageID: id })).toThrow("NotFoundError") - const result = MessageV2.get({ sessionID: a.id, messageID: id }) - expect(result.info.id).toBe(id) - - await svc.remove(a.id) - await svc.remove(b.id) - }, - }) - }) - - test("returns message with multiple parts", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const [id] = await fill(session.id, 1) - - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: id, - type: "text", - text: "extra", - }) - - const result = MessageV2.get({ sessionID: session.id, messageID: id }) - expect(result.parts).toHaveLength(2) - - await svc.remove(session.id) - }, - }) - }) - - test("returns assistant message with correct role", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const uid = await addUser(session.id, "hello") - const aid = await addAssistant(session.id, uid) - - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: aid, - type: "text", - text: "response", - }) - - const result = MessageV2.get({ sessionID: session.id, messageID: aid }) - expect(result.info.role).toBe("assistant") - expect(result.parts).toHaveLength(1) - expect((result.parts[0] as MessageV2.TextPart).text).toBe("response") - - await svc.remove(session.id) - }, - }) - }) - - test("returns message with zero parts", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const id = await addUser(session.id) - - const result = MessageV2.get({ sessionID: session.id, messageID: id }) - expect(result.info.id).toBe(id) - expect(result.parts).toEqual([]) - - await svc.remove(session.id) - }, - }) - }) + it.instance("returns message with hydrated parts", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) + + const result = MessageV2.get({ sessionID, messageID: id }) + expect(result.info.id).toBe(id) + expect(result.info.sessionID).toBe(sessionID) + expect(result.info.role).toBe("user") + expect(result.parts).toHaveLength(1) + expect((result.parts[0] as MessageV2.TextPart).text).toBe("m0") + })), + ) + + it.instance("throws NotFoundError for non-existent message", () => + withSession(({ sessionID }) => Effect.gen(function* () { + expect(() => MessageV2.get({ sessionID, messageID: MessageID.ascending() })).toThrow( + "NotFoundError", + ) + })), + ) + + it.instance("scopes by session id", () => + Effect.gen(function* () { + const session = yield* SessionNs.Service + const a = yield* session.create({}) + const b = yield* session.create({}) + const [id] = yield* fill(a.id, 1) + + expect(() => MessageV2.get({ sessionID: b.id, messageID: id })).toThrow("NotFoundError") + const result = MessageV2.get({ sessionID: a.id, messageID: id }) + expect(result.info.id).toBe(id) + + yield* session.remove(a.id) + yield* session.remove(b.id) + }), + ) + + it.instance("returns message with multiple parts", () => + withSession(({ session, sessionID }) => Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) + + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: "extra", + }) + + const result = MessageV2.get({ sessionID, messageID: id }) + expect(result.parts).toHaveLength(2) + })), + ) + + it.instance("returns assistant message with correct role", () => + withSession(({ session, sessionID }) => Effect.gen(function* () { + const uid = yield* addUser(sessionID, "hello") + const aid = yield* addAssistant(sessionID, uid) + + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: aid, + type: "text", + text: "response", + }) + + const result = MessageV2.get({ sessionID, messageID: aid }) + expect(result.info.role).toBe("assistant") + expect(result.parts).toHaveLength(1) + expect((result.parts[0] as MessageV2.TextPart).text).toBe("response") + })), + ) + + it.instance("returns message with zero parts", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const id = yield* addUser(sessionID) + + const result = MessageV2.get({ sessionID, messageID: id }) + expect(result.info.id).toBe(id) + expect(result.parts).toEqual([]) + })), + ) }) describe("MessageV2.filterCompacted", () => { - test("returns all messages when no compaction", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 5) - - const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(result).toHaveLength(5) - // reversed from newest-first to chronological - expect(result.map((item) => item.info.id)).toEqual(ids) - - await svc.remove(session.id) - }, - }) - }) - - test("stops at compaction boundary and returns chronological order", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - // Chronological: u1(+compaction part), a1(summary, parentID=u1), u2, a2 - // Stream (newest first): a2, u2, a1(adds u1 to completed), u1(in completed + compaction) -> break - const u1 = await addUser(session.id, "first question") - const a1 = await addAssistant(session.id, u1, { summary: true, finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a1, - type: "text", - text: "summary", - }) - await addCompactionPart(session.id, u1) - - const u2 = await addUser(session.id, "new question") - const a2 = await addAssistant(session.id, u2) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a2, - type: "text", - text: "new response", - }) - - const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - // Includes compaction boundary: u1, a1, u2, a2 - expect(result[0].info.id).toBe(u1) - expect(result.length).toBe(4) - - await svc.remove(session.id) - }, - }) - }) - - test("handles empty iterable", () => { + it.instance("returns all messages when no compaction", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const ids = yield* fill(sessionID, 5) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + expect(result).toHaveLength(5) + // reversed from newest-first to chronological + expect(result.map((item) => item.info.id)).toEqual(ids) + })), + ) + + it.instance("stops at compaction boundary and returns chronological order", () => + withSession(({ session, sessionID }) => Effect.gen(function* () { + // Chronological: u1(+compaction part), a1(summary, parentID=u1), u2, a2 + // Stream (newest first): a2, u2, a1(adds u1 to completed), u1(in completed + compaction) -> break + const u1 = yield* addUser(sessionID, "first question") + const a1 = yield* addAssistant(sessionID, u1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a1, + type: "text", + text: "summary", + }) + yield* addCompactionPart(sessionID, u1) + + const u2 = yield* addUser(sessionID, "new question") + const a2 = yield* addAssistant(sessionID, u2) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a2, + type: "text", + text: "new response", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + // Includes compaction boundary: u1, a1, u2, a2 + expect(result[0].info.id).toBe(u1) + expect(result.length).toBe(4) + })), + ) + + it.live("handles empty iterable", () => Effect.sync(() => { const result = MessageV2.filterCompacted([]) expect(result).toEqual([]) - }) - - test("does not break on compaction part without matching summary", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const u1 = await addUser(session.id, "hello") - await addCompactionPart(session.id, u1) - await addUser(session.id, "world") - - const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(result).toHaveLength(2) - - await svc.remove(session.id) - }, - }) - }) - - test("skips assistant with error even if marked as summary", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const u1 = await addUser(session.id, "hello") - await addCompactionPart(session.id, u1) - - const error = new MessageV2.APIError({ - message: "boom", - isRetryable: true, - }).toObject() as MessageV2.Assistant["error"] - await addAssistant(session.id, u1, { summary: true, finish: "end_turn", error }) - await addUser(session.id, "retry") - - const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - // Error assistant doesn't add to completed, so compaction boundary never triggers - expect(result).toHaveLength(3) - - await svc.remove(session.id) - }, - }) - }) - - test("skips assistant without finish even if marked as summary", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const u1 = await addUser(session.id, "hello") - await addCompactionPart(session.id, u1) - - // summary=true but no finish - await addAssistant(session.id, u1, { summary: true }) - await addUser(session.id, "next") - - const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(result).toHaveLength(3) - - await svc.remove(session.id) - }, - }) - }) - - test("ignores original tail when compaction stores tail_start_id", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const u1 = await addUser(session.id, "first") - const a1 = await addAssistant(session.id, u1, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a1, - type: "text", - text: "first reply", - }) - - const u2 = await addUser(session.id, "second") - const a2 = await addAssistant(session.id, u2, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a2, - type: "text", - text: "second reply", - }) - - const c1 = await addUser(session.id) - await addCompactionPart(session.id, c1, u2) - const s1 = await addAssistant(session.id, c1, { summary: true, finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: s1, - type: "text", - text: "summary", - }) - - const u3 = await addUser(session.id, "third") - const a3 = await addAssistant(session.id, u3, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a3, - type: "text", - text: "third reply", - }) - - const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - - expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) - - await svc.remove(session.id) - }, - }) - }) - - test("fork keeps legacy tail_start_id without replaying the tail", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const u1 = await addUser(session.id, "first") - const a1 = await addAssistant(session.id, u1, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a1, - type: "text", - text: "first reply", - }) - - const u2 = await addUser(session.id, "second") - const a2 = await addAssistant(session.id, u2, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a2, - type: "text", - text: "second reply", - }) - - const c1 = await addUser(session.id) - await addCompactionPart(session.id, c1, u2) - const s1 = await addAssistant(session.id, c1, { summary: true, finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: s1, - type: "text", - text: "summary", - }) - - const u3 = await addUser(session.id, "third") - const a3 = await addAssistant(session.id, u3, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a3, - type: "text", - text: "third reply", - }) - - const parentFiltered = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(parentFiltered.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) - - const forked = await svc.fork({ sessionID: session.id }) - const childFiltered = MessageV2.filterCompacted(MessageV2.stream(forked.id)) - expect(childFiltered).toHaveLength(parentFiltered.length) - - const tailPart = childFiltered.flatMap((m) => m.parts).find((p) => p.type === "compaction") - expect(tailPart?.type).toBe("compaction") - if (!tailPart || tailPart.type !== "compaction") throw new Error("Expected forked compaction part") - expect(tailPart.tail_start_id).toBeDefined() - expect(childFiltered.some((m) => m.info.id === tailPart.tail_start_id)).toBe(false) - - await svc.remove(forked.id) - await svc.remove(session.id) - }, - }) - }) - - test("does not replay an assistant tail when compaction starts inside a turn", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const u1 = await addUser(session.id, "first") - const a1 = await addAssistant(session.id, u1, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a1, - type: "text", - text: "first reply", - }) - - const u2 = await addUser(session.id, "second") - const a2 = await addAssistant(session.id, u2, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a2, - type: "text", - text: "second reply", - }) - const a3 = await addAssistant(session.id, u2, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a3, - type: "text", - text: "tail reply", - }) - - const c1 = await addUser(session.id) - await addCompactionPart(session.id, c1, a3) - const s1 = await addAssistant(session.id, c1, { summary: true, finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: s1, - type: "text", - text: "summary", - }) - - const u3 = await addUser(session.id, "third") - const a4 = await addAssistant(session.id, u3, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a4, - type: "text", - text: "third reply", - }) - - const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - - expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a4]) - - await svc.remove(session.id) - }, - }) - }) - - test("prefers latest compaction boundary when repeated compactions exist", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - - const u1 = await addUser(session.id, "first") - const a1 = await addAssistant(session.id, u1, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a1, - type: "text", - text: "first reply", - }) - - const u2 = await addUser(session.id, "second") - const a2 = await addAssistant(session.id, u2, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a2, - type: "text", - text: "second reply", - }) - - const c1 = await addUser(session.id) - await addCompactionPart(session.id, c1, u2) - const s1 = await addAssistant(session.id, c1, { summary: true, finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: s1, - type: "text", - text: "summary one", - }) - - const u3 = await addUser(session.id, "third") - const a3 = await addAssistant(session.id, u3, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a3, - type: "text", - text: "third reply", - }) - - const c2 = await addUser(session.id) - await addCompactionPart(session.id, c2, u3) - const s2 = await addAssistant(session.id, c2, { summary: true, finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: s2, - type: "text", - text: "summary two", - }) - - const u4 = await addUser(session.id, "fourth") - const a4 = await addAssistant(session.id, u4, { finish: "end_turn" }) - await svc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: a4, - type: "text", - text: "fourth reply", - }) - - const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) - - expect(result.map((item) => item.info.id)).toEqual([c2, s2, u4, a4]) - - await svc.remove(session.id) - }, - }) - }) + })) + + it.instance("does not break on compaction part without matching summary", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "hello") + yield* addCompactionPart(sessionID, u1) + yield* addUser(sessionID, "world") + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + expect(result).toHaveLength(2) + })), + ) + + it.instance("skips assistant with error even if marked as summary", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "hello") + yield* addCompactionPart(sessionID, u1) + + const error = new MessageV2.APIError({ + message: "boom", + isRetryable: true, + }).toObject() as MessageV2.Assistant["error"] + yield* addAssistant(sessionID, u1, { summary: true, finish: "end_turn", error }) + yield* addUser(sessionID, "retry") + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + // Error assistant doesn't add to completed, so compaction boundary never triggers + expect(result).toHaveLength(3) + })), + ) + + it.instance("skips assistant without finish even if marked as summary", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "hello") + yield* addCompactionPart(sessionID, u1) + + // summary=true but no finish + yield* addAssistant(sessionID, u1, { summary: true }) + yield* addUser(sessionID, "next") + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + expect(result).toHaveLength(3) + })), + ) + + it.instance("ignores original tail when compaction stores tail_start_id", () => + withSession(({ session, sessionID }) => Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "first") + const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a1, + type: "text", + text: "first reply", + }) + + const u2 = yield* addUser(sessionID, "second") + const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a2, + type: "text", + text: "second reply", + }) + + const c1 = yield* addUser(sessionID) + yield* addCompactionPart(sessionID, c1, u2) + const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: s1, + type: "text", + text: "summary", + }) + + const u3 = yield* addUser(sessionID, "third") + const a3 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a3, + type: "text", + text: "third reply", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + + expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) + })), + ) + + it.instance("fork keeps legacy tail_start_id without replaying the tail", () => + Effect.gen(function* () { + const session = yield* SessionNs.Service + const created = yield* session.create({}) + + const u1 = yield* addUser(created.id, "first") + const a1 = yield* addAssistant(created.id, u1, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID: created.id, + messageID: a1, + type: "text", + text: "first reply", + }) + + const u2 = yield* addUser(created.id, "second") + const a2 = yield* addAssistant(created.id, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID: created.id, + messageID: a2, + type: "text", + text: "second reply", + }) + + const c1 = yield* addUser(created.id) + yield* addCompactionPart(created.id, c1, u2) + const s1 = yield* addAssistant(created.id, c1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID: created.id, + messageID: s1, + type: "text", + text: "summary", + }) + + const u3 = yield* addUser(created.id, "third") + const a3 = yield* addAssistant(created.id, u3, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID: created.id, + messageID: a3, + type: "text", + text: "third reply", + }) + + const parentFiltered = MessageV2.filterCompacted(MessageV2.stream(created.id)) + expect(parentFiltered.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) + + const forked = yield* session.fork({ sessionID: created.id }) + const childFiltered = MessageV2.filterCompacted(MessageV2.stream(forked.id)) + expect(childFiltered).toHaveLength(parentFiltered.length) + + const tailPart = childFiltered.flatMap((m) => m.parts).find((p) => p.type === "compaction") + expect(tailPart?.type).toBe("compaction") + if (!tailPart || tailPart.type !== "compaction") throw new Error("Expected forked compaction part") + expect(tailPart.tail_start_id).toBeDefined() + expect(childFiltered.some((m) => m.info.id === tailPart.tail_start_id)).toBe(false) + + yield* session.remove(forked.id) + yield* session.remove(created.id) + }), + ) + + it.instance("does not replay an assistant tail when compaction starts inside a turn", () => + withSession(({ session, sessionID }) => Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "first") + const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a1, + type: "text", + text: "first reply", + }) + + const u2 = yield* addUser(sessionID, "second") + const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a2, + type: "text", + text: "second reply", + }) + const a3 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a3, + type: "text", + text: "tail reply", + }) + + const c1 = yield* addUser(sessionID) + yield* addCompactionPart(sessionID, c1, a3) + const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: s1, + type: "text", + text: "summary", + }) + + const u3 = yield* addUser(sessionID, "third") + const a4 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a4, + type: "text", + text: "third reply", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + + expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a4]) + })), + ) + + it.instance("prefers latest compaction boundary when repeated compactions exist", () => + withSession(({ session, sessionID }) => Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "first") + const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a1, + type: "text", + text: "first reply", + }) + + const u2 = yield* addUser(sessionID, "second") + const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a2, + type: "text", + text: "second reply", + }) + + const c1 = yield* addUser(sessionID) + yield* addCompactionPart(sessionID, c1, u2) + const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: s1, + type: "text", + text: "summary one", + }) + + const u3 = yield* addUser(sessionID, "third") + const a3 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a3, + type: "text", + text: "third reply", + }) + + const c2 = yield* addUser(sessionID) + yield* addCompactionPart(sessionID, c2, u3) + const s2 = yield* addAssistant(sessionID, c2, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: s2, + type: "text", + text: "summary two", + }) + + const u4 = yield* addUser(sessionID, "fourth") + const a4 = yield* addAssistant(sessionID, u4, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a4, + type: "text", + text: "fourth reply", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + + expect(result.map((item) => item.info.id)).toEqual([c2, s2, u4, a4]) + })), + ) test("works with array input", () => { // filterCompacted accepts any Iterable, not just generators @@ -1093,82 +875,58 @@ describe("MessageV2.cursor", () => { }) describe("MessageV2 consistency", () => { - test("page hydration matches get for each message", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - await fill(session.id, 3) - - const paged = MessageV2.page({ sessionID: session.id, limit: 10 }) - for (const item of paged.items) { - const got = MessageV2.get({ sessionID: session.id, messageID: item.info.id as MessageID }) - expect(got.info).toEqual(item.info) - expect(got.parts).toEqual(item.parts) - } - - await svc.remove(session.id) - }, - }) - }) - - test("parts from get match standalone parts call", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - const [id] = await fill(session.id, 1) - - const got = MessageV2.get({ sessionID: session.id, messageID: id }) - const standalone = MessageV2.parts(id) - expect(got.parts).toEqual(standalone) - - await svc.remove(session.id) - }, - }) - }) - - test("stream collects same messages as exhaustive page iteration", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - await fill(session.id, 7) - - const streamed = Array.from(MessageV2.stream(session.id)) - - const paged = [] as MessageV2.WithParts[] - let cursor: string | undefined - while (true) { - const result = MessageV2.page({ sessionID: session.id, limit: 3, before: cursor }) - for (let i = result.items.length - 1; i >= 0; i--) { - paged.push(result.items[i]) - } - if (!result.more || !result.cursor) break - cursor = result.cursor + it.instance("page hydration matches get for each message", () => + withSession(({ sessionID }) => Effect.gen(function* () { + yield* fill(sessionID, 3) + + const paged = MessageV2.page({ sessionID, limit: 10 }) + for (const item of paged.items) { + const got = MessageV2.get({ sessionID, messageID: item.info.id as MessageID }) + expect(got.info).toEqual(item.info) + expect(got.parts).toEqual(item.parts) + } + })), + ) + + it.instance("parts from get match standalone parts call", () => + withSession(({ sessionID }) => Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) + + const got = MessageV2.get({ sessionID, messageID: id }) + const standalone = MessageV2.parts(id) + expect(got.parts).toEqual(standalone) + })), + ) + + it.instance("stream collects same messages as exhaustive page iteration", () => + withSession(({ sessionID }) => Effect.gen(function* () { + yield* fill(sessionID, 7) + + const streamed = Array.from(MessageV2.stream(sessionID)) + + const paged = [] as MessageV2.WithParts[] + let cursor: string | undefined + while (true) { + const result = MessageV2.page({ sessionID, limit: 3, before: cursor }) + for (let i = result.items.length - 1; i >= 0; i--) { + paged.push(result.items[i]) } + if (!result.more || !result.cursor) break + cursor = result.cursor + } - expect(streamed.map((m) => m.info.id)).toEqual(paged.map((m) => m.info.id)) - - await svc.remove(session.id) - }, - }) - }) - - test("filterCompacted of full stream returns same as Array.from when no compaction", async () => { - await WithInstance.provide({ - directory: root, - fn: async () => { - const session = await svc.create({}) - await fill(session.id, 4) + expect(streamed.map((m) => m.info.id)).toEqual(paged.map((m) => m.info.id)) + })), + ) - const filtered = MessageV2.filterCompacted(MessageV2.stream(session.id)) - const all = Array.from(MessageV2.stream(session.id)).reverse() + it.instance("filterCompacted of full stream returns same as Array.from when no compaction", () => + withSession(({ sessionID }) => Effect.gen(function* () { + yield* fill(sessionID, 4) - expect(filtered.map((m) => m.info.id)).toEqual(all.map((m) => m.info.id)) + const filtered = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + const all = Array.from(MessageV2.stream(sessionID)).reverse() - await svc.remove(session.id) - }, - }) - }) + expect(filtered.map((m) => m.info.id)).toEqual(all.map((m) => m.info.id)) + })), + ) }) From e0e9414cbdc32d3bd08c8c4ea239fb9e80091c8b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 00:41:30 +0000 Subject: [PATCH 060/378] chore: generate --- packages/opencode/test/file/index.test.ts | 56 +- .../test/session/messages-pagination.test.ts | 1058 +++++++++-------- 2 files changed, 608 insertions(+), 506 deletions(-) diff --git a/packages/opencode/test/file/index.test.ts b/packages/opencode/test/file/index.test.ts index 925084140..8b48fff5e 100644 --- a/packages/opencode/test/file/index.test.ts +++ b/packages/opencode/test/file/index.test.ts @@ -115,7 +115,9 @@ describe("file/index Filesystem patterns", () => { it.instance("handles multi-line text files", () => Effect.gen(function* () { const test = yield* TestInstance - yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "multiline.txt"), "line1\nline2\nline3", "utf-8")) + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "multiline.txt"), "line1\nline2\nline3", "utf-8"), + ) const result = yield* read("multiline.txt") expect(result.content).toBe("line1\nline2\nline3") @@ -141,7 +143,9 @@ describe("file/index Filesystem patterns", () => { it.instance("returns empty for binary non-image files", () => Effect.gen(function* () { const test = yield* TestInstance - yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "binary.so"), Buffer.from([0x7f, 0x45, 0x4c, 0x46]))) + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "binary.so"), Buffer.from([0x7f, 0x45, 0x4c, 0x46])), + ) const result = yield* read("binary.so") expect(result.type).toBe("binary") @@ -250,7 +254,9 @@ describe("file/index Filesystem patterns", () => { yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "readonly.txt"), "content", "utf-8")) const nonExistentPath = path.join(test.directory, "does-not-exist.txt") - expect(Exit.isFailure(yield* Effect.promise(() => Filesystem.readText(nonExistentPath)).pipe(Effect.exit))).toBe(true) + expect( + Exit.isFailure(yield* Effect.promise(() => Filesystem.readText(nonExistentPath)).pipe(Effect.exit)), + ).toBe(true) const result = yield* read("does-not-exist.txt") expect(result.content).toBe("") @@ -261,7 +267,9 @@ describe("file/index Filesystem patterns", () => { Effect.gen(function* () { const test = yield* TestInstance const nonExistentPath = path.join(test.directory, "does-not-exist.bin") - const buffer = yield* Effect.promise(() => Filesystem.readArrayBuffer(nonExistentPath).catch(() => new ArrayBuffer(0))) + const buffer = yield* Effect.promise(() => + Filesystem.readArrayBuffer(nonExistentPath).catch(() => new ArrayBuffer(0)), + ) expect(buffer.byteLength).toBe(0) }), ) @@ -279,7 +287,9 @@ describe("file/index Filesystem patterns", () => { it.instance("treats .ts files as text", () => Effect.gen(function* () { const test = yield* TestInstance - yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.ts"), "export const value = 1", "utf-8")) + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "test.ts"), "export const value = 1", "utf-8"), + ) const result = yield* read("test.ts") expect(result.type).toBe("text") @@ -290,7 +300,9 @@ describe("file/index Filesystem patterns", () => { it.instance("treats .mts files as text", () => Effect.gen(function* () { const test = yield* TestInstance - yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.mts"), "export const value = 1", "utf-8")) + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "test.mts"), "export const value = 1", "utf-8"), + ) const result = yield* read("test.mts") expect(result.type).toBe("text") @@ -301,7 +313,9 @@ describe("file/index Filesystem patterns", () => { it.instance("treats .sh files as text", () => Effect.gen(function* () { const test = yield* TestInstance - yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.sh"), "#!/usr/bin/env bash\necho hello", "utf-8")) + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "test.sh"), "#!/usr/bin/env bash\necho hello", "utf-8"), + ) const result = yield* read("test.sh") expect(result.type).toBe("text") @@ -334,7 +348,9 @@ describe("file/index Filesystem patterns", () => { it.instance("returns base64 encoding for images", () => Effect.gen(function* () { const test = yield* TestInstance - yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "test.jpg"), Buffer.from([0xff, 0xd8, 0xff, 0xe0]))) + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "test.jpg"), Buffer.from([0xff, 0xd8, 0xff, 0xe0])), + ) const result = yield* read("test.jpg") expect(result.encoding).toBe("base64") @@ -384,7 +400,9 @@ describe("file/index Filesystem patterns", () => { () => Effect.gen(function* () { const test = yield* TestInstance - yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "new.txt"), "line1\nline2\nline3\n", "utf-8")) + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "new.txt"), "line1\nline2\nline3\n", "utf-8"), + ) const result = yield* status() const entry = result.find((file) => file.path === "new.txt") @@ -457,10 +475,14 @@ describe("file/index Filesystem patterns", () => { Effect.gen(function* () { const test = yield* TestInstance const filepath = path.join(test.directory, "data.bin") - yield* Effect.promise(() => fs.writeFile(filepath, Buffer.from(Array.from({ length: 256 }, (_, index) => index)))) + yield* Effect.promise(() => + fs.writeFile(filepath, Buffer.from(Array.from({ length: 256 }, (_, index) => index))), + ) yield* gitAddAll(test.directory) yield* gitCommit(test.directory, "add binary") - yield* Effect.promise(() => fs.writeFile(filepath, Buffer.from(Array.from({ length: 512 }, (_, index) => index % 256)))) + yield* Effect.promise(() => + fs.writeFile(filepath, Buffer.from(Array.from({ length: 512 }, (_, index) => index % 256))), + ) const result = yield* status() const entry = result.find((file) => file.path === "data.bin") @@ -481,7 +503,9 @@ describe("file/index Filesystem patterns", () => { const test = yield* TestInstance yield* Effect.promise(() => fs.mkdir(path.join(test.directory, "subdir"))) yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "file.txt"), "content", "utf-8")) - yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "subdir", "nested.txt"), "nested", "utf-8")) + yield* Effect.promise(() => + fs.writeFile(path.join(test.directory, "subdir", "nested.txt"), "nested", "utf-8"), + ) const nodes = yield* list() expect(nodes.length).toBeGreaterThanOrEqual(2) @@ -633,8 +657,12 @@ describe("file/index Filesystem patterns", () => { const result = yield* search({ query: "", type: "directory" }) expect(result.length).toBeGreaterThan(0) - const firstHidden = result.findIndex((dir) => dir.split("/").some((part) => part.startsWith(".") && part.length > 1)) - const lastVisible = result.findLastIndex((dir) => !dir.split("/").some((part) => part.startsWith(".") && part.length > 1)) + const firstHidden = result.findIndex((dir) => + dir.split("/").some((part) => part.startsWith(".") && part.length > 1), + ) + const lastVisible = result.findLastIndex( + (dir) => !dir.split("/").some((part) => part.startsWith(".") && part.length > 1), + ) if (firstHidden >= 0 && lastVisible >= 0) { expect(firstHidden).toBeGreaterThan(lastVisible) } diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index 49828a9b6..e1714a901 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -126,53 +126,61 @@ const addCompactionPart = Effect.fn("Test.addCompactionPart")(function* ( describe("MessageV2.page", () => { it.instance("returns sync result", () => - withSession(({ sessionID }) => Effect.gen(function* () { - yield* fill(sessionID, 2) + withSession(({ sessionID }) => + Effect.gen(function* () { + yield* fill(sessionID, 2) - const result = MessageV2.page({ sessionID, limit: 10 }) - expect(result).toBeDefined() - expect(result.items).toBeArray() - })), + const result = MessageV2.page({ sessionID, limit: 10 }) + expect(result).toBeDefined() + expect(result.items).toBeArray() + }), + ), ) it.instance("pages backward with opaque cursors", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 6) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 6) - const a = MessageV2.page({ sessionID, limit: 2 }) - expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) - expect(a.items.every((item) => item.parts.length === 1)).toBe(true) - expect(a.more).toBe(true) - expect(a.cursor).toBeTruthy() + const a = MessageV2.page({ sessionID, limit: 2 }) + expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) + expect(a.items.every((item) => item.parts.length === 1)).toBe(true) + expect(a.more).toBe(true) + expect(a.cursor).toBeTruthy() - const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) - expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) - expect(b.more).toBe(true) - expect(b.cursor).toBeTruthy() + const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) + expect(b.more).toBe(true) + expect(b.cursor).toBeTruthy() - const c = MessageV2.page({ sessionID, limit: 2, before: b.cursor! }) - expect(c.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) - expect(c.more).toBe(false) - expect(c.cursor).toBeUndefined() - })), + const c = MessageV2.page({ sessionID, limit: 2, before: b.cursor! }) + expect(c.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) + expect(c.more).toBe(false) + expect(c.cursor).toBeUndefined() + }), + ), ) it.instance("returns items in chronological order within a page", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 4) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 4) - const result = MessageV2.page({ sessionID, limit: 4 }) - expect(result.items.map((item) => item.info.id)).toEqual(ids) - })), + const result = MessageV2.page({ sessionID, limit: 4 }) + expect(result.items.map((item) => item.info.id)).toEqual(ids) + }), + ), ) it.instance("returns empty items for session with no messages", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const result = MessageV2.page({ sessionID, limit: 10 }) - expect(result.items).toEqual([]) - expect(result.more).toBe(false) - expect(result.cursor).toBeUndefined() - })), + withSession(({ sessionID }) => + Effect.gen(function* () { + const result = MessageV2.page({ sessionID, limit: 10 }) + expect(result.items).toEqual([]) + expect(result.more).toBe(false) + expect(result.cursor).toBeUndefined() + }), + ), ) it.instance("throws NotFoundError for non-existent session", () => @@ -183,69 +191,79 @@ describe("MessageV2.page", () => { ) it.instance("handles exact limit boundary", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 3) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 3) - const result = MessageV2.page({ sessionID, limit: 3 }) - expect(result.items.map((item) => item.info.id)).toEqual(ids) - expect(result.more).toBe(false) - expect(result.cursor).toBeUndefined() - })), + const result = MessageV2.page({ sessionID, limit: 3 }) + expect(result.items.map((item) => item.info.id)).toEqual(ids) + expect(result.more).toBe(false) + expect(result.cursor).toBeUndefined() + }), + ), ) it.instance("limit of 1 returns single newest message", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 5) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 5) - const result = MessageV2.page({ sessionID, limit: 1 }) - expect(result.items).toHaveLength(1) - expect(result.items[0].info.id).toBe(ids[ids.length - 1]) - expect(result.more).toBe(true) - })), + const result = MessageV2.page({ sessionID, limit: 1 }) + expect(result.items).toHaveLength(1) + expect(result.items[0].info.id).toBe(ids[ids.length - 1]) + expect(result.more).toBe(true) + }), + ), ) it.instance("hydrates multiple parts per message", () => - withSession(({ session, sessionID }) => Effect.gen(function* () { - const [id] = yield* fill(sessionID, 1) + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: id, - type: "text", - text: "extra", - }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: "extra", + }) - const result = MessageV2.page({ sessionID, limit: 10 }) - expect(result.items).toHaveLength(1) - expect(result.items[0].parts).toHaveLength(2) - })), + const result = MessageV2.page({ sessionID, limit: 10 }) + expect(result.items).toHaveLength(1) + expect(result.items[0].parts).toHaveLength(2) + }), + ), ) it.instance("accepts cursors from fractional timestamps", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 4, (i: number) => 1000.5 + i) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 4, (i: number) => 1000.5 + i) - const a = MessageV2.page({ sessionID, limit: 2 }) - const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + const a = MessageV2.page({ sessionID, limit: 2 }) + const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) - expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) - expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) - })), + expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) + expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) + }), + ), ) it.instance("messages with same timestamp are ordered by id", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 4, () => 1000) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 4, () => 1000) - const a = MessageV2.page({ sessionID, limit: 2 }) - expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) - expect(a.more).toBe(true) + const a = MessageV2.page({ sessionID, limit: 2 }) + expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) + expect(a.more).toBe(true) - const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) - expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) - expect(b.more).toBe(false) - })), + const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) + expect(b.more).toBe(false) + }), + ), ) it.instance("does not return messages from other sessions", () => @@ -269,128 +287,148 @@ describe("MessageV2.page", () => { ) it.instance("large limit returns all messages without cursor", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 10) - - const result = MessageV2.page({ sessionID, limit: 100 }) - expect(result.items).toHaveLength(10) - expect(result.items.map((item) => item.info.id)).toEqual(ids) - expect(result.more).toBe(false) - expect(result.cursor).toBeUndefined() - })), + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 10) + + const result = MessageV2.page({ sessionID, limit: 100 }) + expect(result.items).toHaveLength(10) + expect(result.items.map((item) => item.info.id)).toEqual(ids) + expect(result.more).toBe(false) + expect(result.cursor).toBeUndefined() + }), + ), ) }) describe("MessageV2.stream", () => { it.instance("yields items newest first", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 5) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 5) - const items = Array.from(MessageV2.stream(sessionID)) - expect(items.map((item) => item.info.id)).toEqual(ids.slice().reverse()) - })), + const items = Array.from(MessageV2.stream(sessionID)) + expect(items.map((item) => item.info.id)).toEqual(ids.slice().reverse()) + }), + ), ) it.instance("yields nothing for empty session", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const items = Array.from(MessageV2.stream(sessionID)) - expect(items).toHaveLength(0) - })), + withSession(({ sessionID }) => + Effect.gen(function* () { + const items = Array.from(MessageV2.stream(sessionID)) + expect(items).toHaveLength(0) + }), + ), ) it.instance("yields single message", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 1) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 1) - const items = Array.from(MessageV2.stream(sessionID)) - expect(items).toHaveLength(1) - expect(items[0].info.id).toBe(ids[0]) - })), + const items = Array.from(MessageV2.stream(sessionID)) + expect(items).toHaveLength(1) + expect(items[0].info.id).toBe(ids[0]) + }), + ), ) it.instance("hydrates parts for each yielded message", () => - withSession(({ sessionID }) => Effect.gen(function* () { - yield* fill(sessionID, 3) - - const items = Array.from(MessageV2.stream(sessionID)) - for (const item of items) { - expect(item.parts).toHaveLength(1) - expect(item.parts[0].type).toBe("text") - } - })), + withSession(({ sessionID }) => + Effect.gen(function* () { + yield* fill(sessionID, 3) + + const items = Array.from(MessageV2.stream(sessionID)) + for (const item of items) { + expect(item.parts).toHaveLength(1) + expect(item.parts[0].type).toBe("text") + } + }), + ), ) it.instance("handles sets exceeding internal page size", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 60) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 60) - const items = Array.from(MessageV2.stream(sessionID)) - expect(items).toHaveLength(60) - expect(items[0].info.id).toBe(ids[ids.length - 1]) - expect(items[59].info.id).toBe(ids[0]) - })), + const items = Array.from(MessageV2.stream(sessionID)) + expect(items).toHaveLength(60) + expect(items[0].info.id).toBe(ids[ids.length - 1]) + expect(items[59].info.id).toBe(ids[0]) + }), + ), ) it.instance("is a sync generator", () => - withSession(({ sessionID }) => Effect.gen(function* () { - yield* fill(sessionID, 1) - - const gen = MessageV2.stream(sessionID) - const first = gen.next() - // sync generator returns { value, done } directly, not a Promise - expect(first).toHaveProperty("value") - expect(first).toHaveProperty("done") - expect(first.done).toBe(false) - })), + withSession(({ sessionID }) => + Effect.gen(function* () { + yield* fill(sessionID, 1) + + const gen = MessageV2.stream(sessionID) + const first = gen.next() + // sync generator returns { value, done } directly, not a Promise + expect(first).toHaveProperty("value") + expect(first).toHaveProperty("done") + expect(first.done).toBe(false) + }), + ), ) }) describe("MessageV2.parts", () => { it.instance("returns parts for a message", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const [id] = yield* fill(sessionID, 1) + withSession(({ sessionID }) => + Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) - const result = MessageV2.parts(id) - expect(result).toHaveLength(1) - expect(result[0].type).toBe("text") - expect((result[0] as MessageV2.TextPart).text).toBe("m0") - })), + const result = MessageV2.parts(id) + expect(result).toHaveLength(1) + expect(result[0].type).toBe("text") + expect((result[0] as MessageV2.TextPart).text).toBe("m0") + }), + ), ) it.instance("returns empty array for message with no parts", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const id = yield* addUser(sessionID) + withSession(({ sessionID }) => + Effect.gen(function* () { + const id = yield* addUser(sessionID) - const result = MessageV2.parts(id) - expect(result).toEqual([]) - })), + const result = MessageV2.parts(id) + expect(result).toEqual([]) + }), + ), ) it.instance("returns multiple parts in order", () => - withSession(({ session, sessionID }) => Effect.gen(function* () { - const [id] = yield* fill(sessionID, 1) - - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: id, - type: "text", - text: "second", - }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: id, - type: "text", - text: "third", - }) - - const result = MessageV2.parts(id) - expect(result).toHaveLength(3) - expect((result[0] as MessageV2.TextPart).text).toBe("m0") - expect((result[1] as MessageV2.TextPart).text).toBe("second") - expect((result[2] as MessageV2.TextPart).text).toBe("third") - })), + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) + + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: "second", + }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: "third", + }) + + const result = MessageV2.parts(id) + expect(result).toHaveLength(3) + expect((result[0] as MessageV2.TextPart).text).toBe("m0") + expect((result[1] as MessageV2.TextPart).text).toBe("second") + expect((result[2] as MessageV2.TextPart).text).toBe("third") + }), + ), ) it.instance("returns empty for non-existent message id", () => @@ -402,36 +440,40 @@ describe("MessageV2.parts", () => { ) it.instance("parts contain sessionID and messageID", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const [id] = yield* fill(sessionID, 1) + withSession(({ sessionID }) => + Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) - const result = MessageV2.parts(id) - expect(result[0].sessionID).toBe(sessionID) - expect(result[0].messageID).toBe(id) - })), + const result = MessageV2.parts(id) + expect(result[0].sessionID).toBe(sessionID) + expect(result[0].messageID).toBe(id) + }), + ), ) }) describe("MessageV2.get", () => { it.instance("returns message with hydrated parts", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const [id] = yield* fill(sessionID, 1) + withSession(({ sessionID }) => + Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) - const result = MessageV2.get({ sessionID, messageID: id }) - expect(result.info.id).toBe(id) - expect(result.info.sessionID).toBe(sessionID) - expect(result.info.role).toBe("user") - expect(result.parts).toHaveLength(1) - expect((result.parts[0] as MessageV2.TextPart).text).toBe("m0") - })), + const result = MessageV2.get({ sessionID, messageID: id }) + expect(result.info.id).toBe(id) + expect(result.info.sessionID).toBe(sessionID) + expect(result.info.role).toBe("user") + expect(result.parts).toHaveLength(1) + expect((result.parts[0] as MessageV2.TextPart).text).toBe("m0") + }), + ), ) it.instance("throws NotFoundError for non-existent message", () => - withSession(({ sessionID }) => Effect.gen(function* () { - expect(() => MessageV2.get({ sessionID, messageID: MessageID.ascending() })).toThrow( - "NotFoundError", - ) - })), + withSession(({ sessionID }) => + Effect.gen(function* () { + expect(() => MessageV2.get({ sessionID, messageID: MessageID.ascending() })).toThrow("NotFoundError") + }), + ), ) it.instance("scopes by session id", () => @@ -451,192 +493,212 @@ describe("MessageV2.get", () => { ) it.instance("returns message with multiple parts", () => - withSession(({ session, sessionID }) => Effect.gen(function* () { - const [id] = yield* fill(sessionID, 1) + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: id, - type: "text", - text: "extra", - }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: "extra", + }) - const result = MessageV2.get({ sessionID, messageID: id }) - expect(result.parts).toHaveLength(2) - })), + const result = MessageV2.get({ sessionID, messageID: id }) + expect(result.parts).toHaveLength(2) + }), + ), ) it.instance("returns assistant message with correct role", () => - withSession(({ session, sessionID }) => Effect.gen(function* () { - const uid = yield* addUser(sessionID, "hello") - const aid = yield* addAssistant(sessionID, uid) - - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: aid, - type: "text", - text: "response", - }) - - const result = MessageV2.get({ sessionID, messageID: aid }) - expect(result.info.role).toBe("assistant") - expect(result.parts).toHaveLength(1) - expect((result.parts[0] as MessageV2.TextPart).text).toBe("response") - })), + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const uid = yield* addUser(sessionID, "hello") + const aid = yield* addAssistant(sessionID, uid) + + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: aid, + type: "text", + text: "response", + }) + + const result = MessageV2.get({ sessionID, messageID: aid }) + expect(result.info.role).toBe("assistant") + expect(result.parts).toHaveLength(1) + expect((result.parts[0] as MessageV2.TextPart).text).toBe("response") + }), + ), ) it.instance("returns message with zero parts", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const id = yield* addUser(sessionID) + withSession(({ sessionID }) => + Effect.gen(function* () { + const id = yield* addUser(sessionID) - const result = MessageV2.get({ sessionID, messageID: id }) - expect(result.info.id).toBe(id) - expect(result.parts).toEqual([]) - })), + const result = MessageV2.get({ sessionID, messageID: id }) + expect(result.info.id).toBe(id) + expect(result.parts).toEqual([]) + }), + ), ) }) describe("MessageV2.filterCompacted", () => { it.instance("returns all messages when no compaction", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const ids = yield* fill(sessionID, 5) + withSession(({ sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 5) - const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - expect(result).toHaveLength(5) - // reversed from newest-first to chronological - expect(result.map((item) => item.info.id)).toEqual(ids) - })), + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + expect(result).toHaveLength(5) + // reversed from newest-first to chronological + expect(result.map((item) => item.info.id)).toEqual(ids) + }), + ), ) it.instance("stops at compaction boundary and returns chronological order", () => - withSession(({ session, sessionID }) => Effect.gen(function* () { - // Chronological: u1(+compaction part), a1(summary, parentID=u1), u2, a2 - // Stream (newest first): a2, u2, a1(adds u1 to completed), u1(in completed + compaction) -> break - const u1 = yield* addUser(sessionID, "first question") - const a1 = yield* addAssistant(sessionID, u1, { summary: true, finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a1, - type: "text", - text: "summary", - }) - yield* addCompactionPart(sessionID, u1) - - const u2 = yield* addUser(sessionID, "new question") - const a2 = yield* addAssistant(sessionID, u2) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a2, - type: "text", - text: "new response", - }) - - const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - // Includes compaction boundary: u1, a1, u2, a2 - expect(result[0].info.id).toBe(u1) - expect(result.length).toBe(4) - })), + withSession(({ session, sessionID }) => + Effect.gen(function* () { + // Chronological: u1(+compaction part), a1(summary, parentID=u1), u2, a2 + // Stream (newest first): a2, u2, a1(adds u1 to completed), u1(in completed + compaction) -> break + const u1 = yield* addUser(sessionID, "first question") + const a1 = yield* addAssistant(sessionID, u1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a1, + type: "text", + text: "summary", + }) + yield* addCompactionPart(sessionID, u1) + + const u2 = yield* addUser(sessionID, "new question") + const a2 = yield* addAssistant(sessionID, u2) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a2, + type: "text", + text: "new response", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + // Includes compaction boundary: u1, a1, u2, a2 + expect(result[0].info.id).toBe(u1) + expect(result.length).toBe(4) + }), + ), + ) + + it.live("handles empty iterable", () => + Effect.sync(() => { + const result = MessageV2.filterCompacted([]) + expect(result).toEqual([]) + }), ) - it.live("handles empty iterable", () => Effect.sync(() => { - const result = MessageV2.filterCompacted([]) - expect(result).toEqual([]) - })) - it.instance("does not break on compaction part without matching summary", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const u1 = yield* addUser(sessionID, "hello") - yield* addCompactionPart(sessionID, u1) - yield* addUser(sessionID, "world") + withSession(({ sessionID }) => + Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "hello") + yield* addCompactionPart(sessionID, u1) + yield* addUser(sessionID, "world") - const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - expect(result).toHaveLength(2) - })), + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + expect(result).toHaveLength(2) + }), + ), ) it.instance("skips assistant with error even if marked as summary", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const u1 = yield* addUser(sessionID, "hello") - yield* addCompactionPart(sessionID, u1) + withSession(({ sessionID }) => + Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "hello") + yield* addCompactionPart(sessionID, u1) - const error = new MessageV2.APIError({ - message: "boom", - isRetryable: true, - }).toObject() as MessageV2.Assistant["error"] - yield* addAssistant(sessionID, u1, { summary: true, finish: "end_turn", error }) - yield* addUser(sessionID, "retry") + const error = new MessageV2.APIError({ + message: "boom", + isRetryable: true, + }).toObject() as MessageV2.Assistant["error"] + yield* addAssistant(sessionID, u1, { summary: true, finish: "end_turn", error }) + yield* addUser(sessionID, "retry") - const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - // Error assistant doesn't add to completed, so compaction boundary never triggers - expect(result).toHaveLength(3) - })), + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + // Error assistant doesn't add to completed, so compaction boundary never triggers + expect(result).toHaveLength(3) + }), + ), ) it.instance("skips assistant without finish even if marked as summary", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const u1 = yield* addUser(sessionID, "hello") - yield* addCompactionPart(sessionID, u1) + withSession(({ sessionID }) => + Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "hello") + yield* addCompactionPart(sessionID, u1) - // summary=true but no finish - yield* addAssistant(sessionID, u1, { summary: true }) - yield* addUser(sessionID, "next") + // summary=true but no finish + yield* addAssistant(sessionID, u1, { summary: true }) + yield* addUser(sessionID, "next") - const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - expect(result).toHaveLength(3) - })), + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + expect(result).toHaveLength(3) + }), + ), ) it.instance("ignores original tail when compaction stores tail_start_id", () => - withSession(({ session, sessionID }) => Effect.gen(function* () { - const u1 = yield* addUser(sessionID, "first") - const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a1, - type: "text", - text: "first reply", - }) - - const u2 = yield* addUser(sessionID, "second") - const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a2, - type: "text", - text: "second reply", - }) - - const c1 = yield* addUser(sessionID) - yield* addCompactionPart(sessionID, c1, u2) - const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: s1, - type: "text", - text: "summary", - }) - - const u3 = yield* addUser(sessionID, "third") - const a3 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a3, - type: "text", - text: "third reply", - }) - - const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - - expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) - })), + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "first") + const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a1, + type: "text", + text: "first reply", + }) + + const u2 = yield* addUser(sessionID, "second") + const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a2, + type: "text", + text: "second reply", + }) + + const c1 = yield* addUser(sessionID) + yield* addCompactionPart(sessionID, c1, u2) + const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: s1, + type: "text", + text: "summary", + }) + + const u3 = yield* addUser(sessionID, "third") + const a3 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a3, + type: "text", + text: "third reply", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + + expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) + }), + ), ) it.instance("fork keeps legacy tail_start_id without replaying the tail", () => @@ -704,130 +766,134 @@ describe("MessageV2.filterCompacted", () => { ) it.instance("does not replay an assistant tail when compaction starts inside a turn", () => - withSession(({ session, sessionID }) => Effect.gen(function* () { - const u1 = yield* addUser(sessionID, "first") - const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a1, - type: "text", - text: "first reply", - }) - - const u2 = yield* addUser(sessionID, "second") - const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a2, - type: "text", - text: "second reply", - }) - const a3 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a3, - type: "text", - text: "tail reply", - }) - - const c1 = yield* addUser(sessionID) - yield* addCompactionPart(sessionID, c1, a3) - const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: s1, - type: "text", - text: "summary", - }) - - const u3 = yield* addUser(sessionID, "third") - const a4 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a4, - type: "text", - text: "third reply", - }) - - const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - - expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a4]) - })), + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "first") + const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a1, + type: "text", + text: "first reply", + }) + + const u2 = yield* addUser(sessionID, "second") + const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a2, + type: "text", + text: "second reply", + }) + const a3 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a3, + type: "text", + text: "tail reply", + }) + + const c1 = yield* addUser(sessionID) + yield* addCompactionPart(sessionID, c1, a3) + const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: s1, + type: "text", + text: "summary", + }) + + const u3 = yield* addUser(sessionID, "third") + const a4 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a4, + type: "text", + text: "third reply", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + + expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a4]) + }), + ), ) it.instance("prefers latest compaction boundary when repeated compactions exist", () => - withSession(({ session, sessionID }) => Effect.gen(function* () { - const u1 = yield* addUser(sessionID, "first") - const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a1, - type: "text", - text: "first reply", - }) - - const u2 = yield* addUser(sessionID, "second") - const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a2, - type: "text", - text: "second reply", - }) - - const c1 = yield* addUser(sessionID) - yield* addCompactionPart(sessionID, c1, u2) - const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: s1, - type: "text", - text: "summary one", - }) - - const u3 = yield* addUser(sessionID, "third") - const a3 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a3, - type: "text", - text: "third reply", - }) - - const c2 = yield* addUser(sessionID) - yield* addCompactionPart(sessionID, c2, u3) - const s2 = yield* addAssistant(sessionID, c2, { summary: true, finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: s2, - type: "text", - text: "summary two", - }) - - const u4 = yield* addUser(sessionID, "fourth") - const a4 = yield* addAssistant(sessionID, u4, { finish: "end_turn" }) - yield* session.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: a4, - type: "text", - text: "fourth reply", - }) - - const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - - expect(result.map((item) => item.info.id)).toEqual([c2, s2, u4, a4]) - })), + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const u1 = yield* addUser(sessionID, "first") + const a1 = yield* addAssistant(sessionID, u1, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a1, + type: "text", + text: "first reply", + }) + + const u2 = yield* addUser(sessionID, "second") + const a2 = yield* addAssistant(sessionID, u2, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a2, + type: "text", + text: "second reply", + }) + + const c1 = yield* addUser(sessionID) + yield* addCompactionPart(sessionID, c1, u2) + const s1 = yield* addAssistant(sessionID, c1, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: s1, + type: "text", + text: "summary one", + }) + + const u3 = yield* addUser(sessionID, "third") + const a3 = yield* addAssistant(sessionID, u3, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a3, + type: "text", + text: "third reply", + }) + + const c2 = yield* addUser(sessionID) + yield* addCompactionPart(sessionID, c2, u3) + const s2 = yield* addAssistant(sessionID, c2, { summary: true, finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: s2, + type: "text", + text: "summary two", + }) + + const u4 = yield* addUser(sessionID, "fourth") + const a4 = yield* addAssistant(sessionID, u4, { finish: "end_turn" }) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: a4, + type: "text", + text: "fourth reply", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + + expect(result.map((item) => item.info.id)).toEqual([c2, s2, u4, a4]) + }), + ), ) test("works with array input", () => { @@ -876,57 +942,65 @@ describe("MessageV2.cursor", () => { describe("MessageV2 consistency", () => { it.instance("page hydration matches get for each message", () => - withSession(({ sessionID }) => Effect.gen(function* () { - yield* fill(sessionID, 3) - - const paged = MessageV2.page({ sessionID, limit: 10 }) - for (const item of paged.items) { - const got = MessageV2.get({ sessionID, messageID: item.info.id as MessageID }) - expect(got.info).toEqual(item.info) - expect(got.parts).toEqual(item.parts) - } - })), + withSession(({ sessionID }) => + Effect.gen(function* () { + yield* fill(sessionID, 3) + + const paged = MessageV2.page({ sessionID, limit: 10 }) + for (const item of paged.items) { + const got = MessageV2.get({ sessionID, messageID: item.info.id as MessageID }) + expect(got.info).toEqual(item.info) + expect(got.parts).toEqual(item.parts) + } + }), + ), ) it.instance("parts from get match standalone parts call", () => - withSession(({ sessionID }) => Effect.gen(function* () { - const [id] = yield* fill(sessionID, 1) + withSession(({ sessionID }) => + Effect.gen(function* () { + const [id] = yield* fill(sessionID, 1) - const got = MessageV2.get({ sessionID, messageID: id }) - const standalone = MessageV2.parts(id) - expect(got.parts).toEqual(standalone) - })), + const got = MessageV2.get({ sessionID, messageID: id }) + const standalone = MessageV2.parts(id) + expect(got.parts).toEqual(standalone) + }), + ), ) it.instance("stream collects same messages as exhaustive page iteration", () => - withSession(({ sessionID }) => Effect.gen(function* () { - yield* fill(sessionID, 7) - - const streamed = Array.from(MessageV2.stream(sessionID)) - - const paged = [] as MessageV2.WithParts[] - let cursor: string | undefined - while (true) { - const result = MessageV2.page({ sessionID, limit: 3, before: cursor }) - for (let i = result.items.length - 1; i >= 0; i--) { - paged.push(result.items[i]) + withSession(({ sessionID }) => + Effect.gen(function* () { + yield* fill(sessionID, 7) + + const streamed = Array.from(MessageV2.stream(sessionID)) + + const paged = [] as MessageV2.WithParts[] + let cursor: string | undefined + while (true) { + const result = MessageV2.page({ sessionID, limit: 3, before: cursor }) + for (let i = result.items.length - 1; i >= 0; i--) { + paged.push(result.items[i]) + } + if (!result.more || !result.cursor) break + cursor = result.cursor } - if (!result.more || !result.cursor) break - cursor = result.cursor - } - expect(streamed.map((m) => m.info.id)).toEqual(paged.map((m) => m.info.id)) - })), + expect(streamed.map((m) => m.info.id)).toEqual(paged.map((m) => m.info.id)) + }), + ), ) it.instance("filterCompacted of full stream returns same as Array.from when no compaction", () => - withSession(({ sessionID }) => Effect.gen(function* () { - yield* fill(sessionID, 4) + withSession(({ sessionID }) => + Effect.gen(function* () { + yield* fill(sessionID, 4) - const filtered = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - const all = Array.from(MessageV2.stream(sessionID)).reverse() + const filtered = MessageV2.filterCompacted(MessageV2.stream(sessionID)) + const all = Array.from(MessageV2.stream(sessionID)).reverse() - expect(filtered.map((m) => m.info.id)).toEqual(all.map((m) => m.info.id)) - })), + expect(filtered.map((m) => m.info.id)).toEqual(all.map((m) => m.info.id)) + }), + ), ) }) From 5773d43cbf356b2ede8e9c000ae5f8bfbf017e75 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 11 May 2026 19:50:35 -0500 Subject: [PATCH 061/378] ci: GitHub Actions dependencies (#26962) --- .github/actions/setup-bun/action.yml | 4 +- .../actions/setup-git-committer/action.yml | 2 +- .github/workflows/beta.yml | 2 +- .github/workflows/close-issues.yml | 4 +- .github/workflows/close-stale-prs.yml | 2 +- .github/workflows/compliance-close.yml | 2 +- .github/workflows/containers.yml | 8 +-- .github/workflows/deploy.yml | 4 +- .github/workflows/docs-locale-sync.yml | 2 +- .github/workflows/docs-update.yml | 4 +- .github/workflows/duplicate-issues.yml | 4 +- .github/workflows/generate.yml | 2 +- .github/workflows/nix-eval.yml | 4 +- .github/workflows/nix-hashes.yml | 10 ++-- .github/workflows/notify-discord.yml | 2 +- .github/workflows/opencode.yml | 4 +- .github/workflows/pr-management.yml | 4 +- .github/workflows/pr-standards.yml | 4 +- .github/workflows/publish-github-action.yml | 2 +- .github/workflows/publish-vscode.yml | 2 +- .github/workflows/publish.yml | 52 +++++++++---------- .github/workflows/release-github-action.yml | 2 +- .github/workflows/review.yml | 2 +- .github/workflows/stats.yml | 2 +- .github/workflows/storybook.yml | 2 +- .github/workflows/sync-zed-extension.yml | 2 +- .github/workflows/test.yml | 18 +++---- .github/workflows/triage.yml | 2 +- .github/workflows/typecheck.yml | 2 +- 29 files changed, 78 insertions(+), 78 deletions(-) diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 9859174a2..35f42462b 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -23,7 +23,7 @@ runs: fi - name: Setup Bun - uses: oven-sh/setup-bun@v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }} bun-download-url: ${{ steps.bun-url.outputs.url }} @@ -34,7 +34,7 @@ runs: run: echo "dir=$(bun pm cache)" >> "$GITHUB_OUTPUT" - name: Cache Bun dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ steps.cache.outputs.dir }} key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} diff --git a/.github/actions/setup-git-committer/action.yml b/.github/actions/setup-git-committer/action.yml index 87d2f5d0d..65c974c6a 100644 --- a/.github/actions/setup-git-committer/action.yml +++ b/.github/actions/setup-git-committer/action.yml @@ -19,7 +19,7 @@ runs: steps: - name: Create app token id: apptoken - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2 with: app-id: ${{ inputs.opencode-app-id }} private-key: ${{ inputs.opencode-app-secret }} diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index a7106667b..e93d5fbdb 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 diff --git a/.github/workflows/close-issues.yml b/.github/workflows/close-issues.yml index 04b6ae7ac..b8a2e3f57 100644 --- a/.github/workflows/close-issues.yml +++ b/.github/workflows/close-issues.yml @@ -12,9 +12,9 @@ jobs: contents: read issues: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: oven-sh/setup-bun@v2 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: latest diff --git a/.github/workflows/close-stale-prs.yml b/.github/workflows/close-stale-prs.yml index e0e571b46..3a0fa4b5c 100644 --- a/.github/workflows/close-stale-prs.yml +++ b/.github/workflows/close-stale-prs.yml @@ -21,7 +21,7 @@ jobs: timeout-minutes: 15 steps: - name: Close inactive PRs - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/compliance-close.yml b/.github/workflows/compliance-close.yml index c3bcf9f68..14e68701e 100644 --- a/.github/workflows/compliance-close.yml +++ b/.github/workflows/compliance-close.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Close non-compliant issues and PRs after 2 hours - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const { data: items } = await github.rest.issues.listForRepo({ diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml index c7df066d4..15bf07831 100644 --- a/.github/workflows/containers.yml +++ b/.github/workflows/containers.yml @@ -21,18 +21,18 @@ jobs: REGISTRY: ghcr.io/${{ github.repository_owner }} TAG: "24.04" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - uses: ./.github/actions/setup-bun - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index abd8bafdd..7b4f53a98 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,11 +13,11 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - uses: ./.github/actions/setup-bun - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "24" diff --git a/.github/workflows/docs-locale-sync.yml b/.github/workflows/docs-locale-sync.yml index 9689eee6d..5f921e8bb 100644 --- a/.github/workflows/docs-locale-sync.yml +++ b/.github/workflows/docs-locale-sync.yml @@ -16,7 +16,7 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false fetch-depth: 0 diff --git a/.github/workflows/docs-update.yml b/.github/workflows/docs-update.yml index 900ad2b0c..4767dec53 100644 --- a/.github/workflows/docs-update.yml +++ b/.github/workflows/docs-update.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 # Fetch full history to access commits @@ -43,7 +43,7 @@ jobs: - name: Run opencode if: steps.commits.outputs.has_commits == 'true' - uses: sst/opencode/github@latest + uses: sst/opencode/github@2c14fc5586fe0b88e5c04732d2e846769cc35671 # latest env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} with: diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index 6c1943fe7..4648a2d0c 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -13,7 +13,7 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 1 @@ -125,7 +125,7 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 1 diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 706ab2989..324cfec02 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Setup Bun uses: ./.github/actions/setup-bun diff --git a/.github/workflows/nix-eval.yml b/.github/workflows/nix-eval.yml index c76b2c972..75332695a 100644 --- a/.github/workflows/nix-eval.yml +++ b/.github/workflows/nix-eval.yml @@ -20,10 +20,10 @@ jobs: timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Nix - uses: nixbuild/nix-quick-install-action@v34 + uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 - name: Evaluate flake outputs (all systems) run: | diff --git a/.github/workflows/nix-hashes.yml b/.github/workflows/nix-hashes.yml index 6b5b3929a..085f8895c 100644 --- a/.github/workflows/nix-hashes.yml +++ b/.github/workflows/nix-hashes.yml @@ -41,10 +41,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Nix - uses: nixbuild/nix-quick-install-action@v34 + uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 - name: Compute node_modules hash id: hash @@ -72,7 +72,7 @@ jobs: echo "Computed hash for ${SYSTEM}: $HASH" - name: Upload hash - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: hash-${{ matrix.system }} path: hash.txt @@ -85,7 +85,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false fetch-depth: 0 @@ -102,7 +102,7 @@ jobs: git pull --rebase --autostash origin "$GITHUB_REF_NAME" - name: Download hash artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: hashes pattern: hash-* diff --git a/.github/workflows/notify-discord.yml b/.github/workflows/notify-discord.yml index b1d805360..0b2b1cde0 100644 --- a/.github/workflows/notify-discord.yml +++ b/.github/workflows/notify-discord.yml @@ -9,6 +9,6 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - name: Send nicely-formatted embed to Discord - uses: SethCohen/github-releases-to-discord@v1 + uses: SethCohen/github-releases-to-discord@24d166886aee4646d448c8a389ff9e1ebcab3682 # v1.20.0 with: webhook_url: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index 76e75fcae..3469c2191 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -21,12 +21,12 @@ jobs: issues: read steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - uses: ./.github/actions/setup-bun - name: Run opencode - uses: anomalyco/opencode/github@latest + uses: anomalyco/opencode/github@2c14fc5586fe0b88e5c04732d2e846769cc35671 # latest env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_PERMISSION: '{"bash": "deny"}' diff --git a/.github/workflows/pr-management.yml b/.github/workflows/pr-management.yml index 35bd7ae36..b6aa4e589 100644 --- a/.github/workflows/pr-management.yml +++ b/.github/workflows/pr-management.yml @@ -12,7 +12,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 1 @@ -78,7 +78,7 @@ jobs: issues: write steps: - name: Add Contributor Label - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const isPR = !!context.payload.pull_request; diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml index 1edbd5d06..06838089d 100644 --- a/.github/workflows/pr-standards.yml +++ b/.github/workflows/pr-standards.yml @@ -12,7 +12,7 @@ jobs: pull-requests: write steps: - name: Check PR standards - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const pr = context.payload.pull_request; @@ -159,7 +159,7 @@ jobs: pull-requests: write steps: - name: Check PR template compliance - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const pr = context.payload.pull_request; diff --git a/.github/workflows/publish-github-action.yml b/.github/workflows/publish-github-action.yml index d2789373a..e5ca91b56 100644 --- a/.github/workflows/publish-github-action.yml +++ b/.github/workflows/publish-github-action.yml @@ -16,7 +16,7 @@ jobs: publish: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: fetch-depth: 0 diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml index f49a10578..00c7e2604 100644 --- a/.github/workflows/publish-vscode.yml +++ b/.github/workflows/publish-vscode.yml @@ -15,7 +15,7 @@ jobs: publish: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: fetch-depth: 0 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5f7ee96b9..bef1e7029 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,7 +35,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2404 if: github.repository == 'anomalyco/opencode' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: fetch-depth: 0 @@ -72,7 +72,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2404 if: github.repository == 'anomalyco/opencode' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: fetch-tags: true @@ -95,14 +95,14 @@ jobs: GH_REPO: ${{ needs.version.outputs.repo }} GH_TOKEN: ${{ steps.committer.outputs.token }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: opencode-cli path: | packages/opencode/dist/opencode-darwin* packages/opencode/dist/opencode-linux* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: opencode-cli-windows path: packages/opencode/dist/opencode-windows* @@ -123,9 +123,9 @@ jobs: AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }} AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: opencode-cli-windows path: packages/opencode/dist @@ -138,13 +138,13 @@ jobs: opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - name: Azure login - uses: azure/login@v2 + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} - - uses: azure/artifact-signing-action@v1 + - uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1.2.0 with: endpoint: ${{ env.AZURE_TRUSTED_SIGNING_ENDPOINT }} signing-account-name: ${{ env.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} @@ -201,7 +201,7 @@ jobs: --clobber ` --repo "${{ needs.version.outputs.repo }}" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: opencode-cli-signed-windows path: | @@ -249,9 +249,9 @@ jobs: platform_flag: --linux runs-on: ${{ matrix.settings.host }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - - uses: apple-actions/import-codesign-certs@v2 + - uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0 if: runner.os == 'macOS' with: keychain: build @@ -268,19 +268,19 @@ jobs: - name: Azure login if: runner.os == 'Windows' - uses: azure/login@v2 + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "24" - name: Cache apt packages if: contains(matrix.settings.host, 'ubuntu') - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/apt-cache key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }} @@ -388,12 +388,12 @@ jobs: } } - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: opencode-desktop-${{ matrix.settings.target }} path: packages/desktop/dist/* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: needs.version.outputs.release with: name: latest-yml-${{ matrix.settings.target }} @@ -408,44 +408,44 @@ jobs: if: always() && !failure() && !cancelled() runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - uses: ./.github/actions/setup-bun - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "24" registry-url: "https://registry.npmjs.org" - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: opencode-cli path: packages/opencode/dist - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: opencode-cli-windows path: packages/opencode/dist - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: opencode-cli-signed-windows path: packages/opencode/dist - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 if: needs.version.outputs.release with: pattern: latest-yml-* @@ -459,7 +459,7 @@ jobs: opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - name: Cache apt packages (AUR) - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: /var/cache/apt/archives key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }} diff --git a/.github/workflows/release-github-action.yml b/.github/workflows/release-github-action.yml index 3f5caa55c..4a1d7218b 100644 --- a/.github/workflows/release-github-action.yml +++ b/.github/workflows/release-github-action.yml @@ -16,7 +16,7 @@ jobs: release: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 2bd1f0c4a..00a4fba8c 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -25,7 +25,7 @@ jobs: fi - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 1 diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml index 824733901..bc97cfcd7 100644 --- a/.github/workflows/stats.yml +++ b/.github/workflows/stats.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Setup Bun uses: ./.github/actions/setup-bun diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index 6d143a8a2..1e652104d 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -29,7 +29,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Setup Bun uses: ./.github/actions/setup-bun diff --git a/.github/workflows/sync-zed-extension.yml b/.github/workflows/sync-zed-extension.yml index f14487cde..6e4b44083 100644 --- a/.github/workflows/sync-zed-extension.yml +++ b/.github/workflows/sync-zed-extension.yml @@ -10,7 +10,7 @@ jobs: name: Release Zed Extension runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f226d3483..4a65b9927 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,12 +37,12 @@ jobs: shell: bash steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "24" @@ -55,7 +55,7 @@ jobs: git config --global user.name "opencode" - name: Cache Turbo - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: node_modules/.cache/turbo key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-${{ github.sha }} @@ -75,7 +75,7 @@ jobs: - name: Publish unit reports if: always() - uses: mikepenz/action-junit-report@v6 + uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6.4.0 with: report_paths: packages/*/.artifacts/unit/junit.xml check_name: "unit results (${{ matrix.settings.name }})" @@ -85,7 +85,7 @@ jobs: - name: Upload unit artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }} include-hidden-files: true @@ -111,12 +111,12 @@ jobs: shell: bash steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "24" @@ -131,7 +131,7 @@ jobs: - name: Cache Playwright browsers id: playwright-cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ github.workspace }}/.playwright-browsers key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright-version.outputs.version }}-chromium @@ -155,7 +155,7 @@ jobs: - name: Upload Playwright artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }} if-no-files-found: ignore diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 99e7b5b34..27852a12c 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -12,7 +12,7 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 1 diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index b247d24b4..fc9a52797 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -12,7 +12,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Setup Bun uses: ./.github/actions/setup-bun From 0d9c5341846819db96b2a7e08f0357d6c24507fa Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 20:59:31 -0400 Subject: [PATCH 062/378] test(snapshot): migrate snapshot tests to Effect runner (#26964) --- .../opencode/test/snapshot/snapshot.test.ts | 2460 +++++++---------- 1 file changed, 1023 insertions(+), 1437 deletions(-) diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts index 99ddfe72d..fa167281b 100644 --- a/packages/opencode/test/snapshot/snapshot.test.ts +++ b/packages/opencode/test/snapshot/snapshot.test.ts @@ -1,13 +1,15 @@ -import { afterEach, test, expect } from "bun:test" +import { afterEach, expect } from "bun:test" import { $ } from "bun" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import fs from "fs/promises" import path from "path" -import { Effect } from "effect" +import { Effect, Fiber } from "effect" import { Snapshot } from "../../src/snapshot" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { Filesystem } from "@/util/filesystem" -import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" + +const it = testEffect(Snapshot.defaultLayer) // Git always outputs /-separated paths internally. Snapshot.patch() joins them // with path.join (which produces \ on Windows) then normalizes back to /. @@ -18,1515 +20,1099 @@ afterEach(async () => { await disposeAllInstances() }) -async function bootstrap() { - return tmpdir({ - git: true, - init: async (dir) => { - const unique = Math.random().toString(36).slice(2) - const aContent = `A${unique}` - const bContent = `B${unique}` - await Filesystem.write(`${dir}/a.txt`, aContent) - await Filesystem.write(`${dir}/b.txt`, bContent) - await $`git add .`.cwd(dir).quiet() - await $`git commit -m init`.cwd(dir).quiet() - return { - aContent, - bContent, - } - }, +const exec = (cwd: string, command: string[]) => + Effect.promise(async () => { + const proc = Bun.spawn(command, { cwd, stdout: "ignore", stderr: "pipe" }) + const code = await proc.exited + if (code !== 0) throw new Error(`${command.join(" ")} failed: ${await new Response(proc.stderr).text()}`) }) -} -function run(dir: string, body: (snapshot: Snapshot.Interface) => Effect.Effect) { - return Effect.runPromise( - Effect.gen(function* () { - const snapshot = yield* Snapshot.Service - return yield* body(snapshot) - }).pipe(provideInstance(dir), Effect.provide(Snapshot.defaultLayer)), +const write = (file: string, content: string | Uint8Array) => Effect.promise(() => Filesystem.write(file, content)) +const readText = (file: string) => Effect.promise(() => fs.readFile(file, "utf-8")) +const exists = (file: string) => + Effect.promise(() => + fs + .access(file) + .then(() => true) + .catch(() => false), ) -} - -test("tracks deleted files correctly", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await $`rm ${tmp.path}/a.txt`.quiet() - - expect((await run(tmp.path, (snapshot) => snapshot.patch(before!))).files).toContain(fwd(tmp.path, "a.txt")) - }, - }) +const mkdirp = (dir: string) => Effect.promise(() => fs.mkdir(dir, { recursive: true })) +const rm = (file: string) => Effect.promise(() => fs.rm(file, { recursive: true, force: true })) + +const initialize = Effect.fn("SnapshotTest.initialize")(function* (dir: string) { + const unique = Math.random().toString(36).slice(2) + const aContent = `A${unique}` + const bContent = `B${unique}` + yield* write(`${dir}/a.txt`, aContent) + yield* write(`${dir}/b.txt`, bContent) + yield* exec(dir, ["git", "add", "."]) + yield* exec(dir, ["git", "commit", "-m", "init"]) + return { aContent, bContent } }) -test("revert should remove new files", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/new.txt`, "NEW") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) +type Bootstrapped = { path: string; extra: { aContent: string; bContent: string } } - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - expect( - await fs - .access(`${tmp.path}/new.txt`) - .then(() => true) - .catch(() => false), - ).toBe(false) - }, - }) +const bootstrap = Effect.fn("SnapshotTest.bootstrap")(function* () { + const tmp = yield* TestInstance + return { path: tmp.directory, extra: yield* initialize(tmp.directory) } }) -test("revert in subdirectory", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await $`mkdir -p ${tmp.path}/sub`.quiet() - await Filesystem.write(`${tmp.path}/sub/file.txt`, "SUB") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - expect( - await fs - .access(`${tmp.path}/sub/file.txt`) - .then(() => true) - .catch(() => false), - ).toBe(false) - // Note: revert currently only removes files, not directories - // The empty subdirectory will remain - }, +const withTrackedSnapshot = ( + fn: (input: { tmp: Bootstrapped; snapshot: Snapshot.Interface; before: string }) => Effect.Effect, +) => + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.track() + expect(before).toBeTruthy() + return yield* fn({ tmp, snapshot, before: before! }) }) -}) - -test("multiple file operations", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - await $`rm ${tmp.path}/a.txt`.quiet() - await Filesystem.write(`${tmp.path}/c.txt`, "C") - await $`mkdir -p ${tmp.path}/dir`.quiet() - await Filesystem.write(`${tmp.path}/dir/d.txt`, "D") - await Filesystem.write(`${tmp.path}/b.txt`, "MODIFIED") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - expect(await fs.readFile(`${tmp.path}/a.txt`, "utf-8")).toBe(tmp.extra.aContent) - expect( - await fs - .access(`${tmp.path}/c.txt`) - .then(() => true) - .catch(() => false), - ).toBe(false) - // Note: revert currently only removes files, not directories - // The empty directory will remain - expect(await fs.readFile(`${tmp.path}/b.txt`, "utf-8")).toBe(tmp.extra.bContent) - }, - }) +const bootstrapScoped = Effect.fn("SnapshotTest.bootstrapScoped")(function* () { + const dir = yield* tmpdirScoped({ git: true }).pipe(Effect.provide(CrossSpawnSpawner.defaultLayer)) + return { path: dir, extra: yield* initialize(dir) } }) -test("empty directory handling", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await $`mkdir ${tmp.path}/empty`.quiet() +const scopedGitTmpdir = () => tmpdirScoped({ git: true }).pipe(Effect.provide(CrossSpawnSpawner.defaultLayer)) - expect((await run(tmp.path, (snapshot) => snapshot.patch(before!))).files.length).toBe(0) - }, +const cleanupWorktree = (repo: string, worktree: string, files: string[] = []) => + Effect.promise(async () => { + await $`git worktree remove --force ${worktree}`.cwd(repo).quiet().nothrow() + await fs.rm(worktree, { recursive: true, force: true }).catch(() => undefined) + await Promise.all(files.map((file) => fs.rm(file, { recursive: true, force: true }).catch(() => undefined))) }) -}) - -test("binary file handling", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - await Filesystem.write(`${tmp.path}/image.png`, new Uint8Array([0x89, 0x50, 0x4e, 0x47])) +const withGitConfigGlobal = (config: string, self: Effect.Effect) => + Effect.acquireUseRelease( + Effect.sync(() => { + const previous = process.env.GIT_CONFIG_GLOBAL + process.env.GIT_CONFIG_GLOBAL = config + return previous + }), + () => self, + (previous) => + Effect.sync(() => { + if (previous) process.env.GIT_CONFIG_GLOBAL = previous + else delete process.env.GIT_CONFIG_GLOBAL + }), + ) - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) +it.instance( + "tracks deleted files correctly", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* rm(`${tmp.path}/a.txt`) + expect((yield* snapshot.patch(before)).files).toContain(fwd(tmp.path, "a.txt")) + }), + ), + { git: true }, +) + +it.instance( + "revert should remove new files", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/new.txt`, "NEW") + const patch = yield* snapshot.patch(before) + yield* snapshot.revert([patch]) + expect(yield* exists(`${tmp.path}/new.txt`)).toBe(false) + }), + ), + { git: true }, +) + +it.instance( + "revert in subdirectory", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* mkdirp(`${tmp.path}/sub`) + yield* write(`${tmp.path}/sub/file.txt`, "SUB") + const patch = yield* snapshot.patch(before) + yield* snapshot.revert([patch]) + expect(yield* exists(`${tmp.path}/sub/file.txt`)).toBe(false) + }), + ), + { git: true }, +) + +it.instance( + "multiple file operations", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* rm(`${tmp.path}/a.txt`) + yield* write(`${tmp.path}/c.txt`, "C") + yield* mkdirp(`${tmp.path}/dir`) + yield* write(`${tmp.path}/dir/d.txt`, "D") + yield* write(`${tmp.path}/b.txt`, "MODIFIED") + const patch = yield* snapshot.patch(before) + yield* snapshot.revert([patch]) + expect(yield* readText(`${tmp.path}/a.txt`)).toBe(tmp.extra.aContent) + expect(yield* exists(`${tmp.path}/c.txt`)).toBe(false) + expect(yield* readText(`${tmp.path}/b.txt`)).toBe(tmp.extra.bContent) + }), + ), + { git: true }, +) + +it.instance( + "empty directory handling", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* mkdirp(`${tmp.path}/empty`) + expect((yield* snapshot.patch(before)).files.length).toBe(0) + }), + ), + { git: true }, +) + +it.instance( + "binary file handling", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/image.png`, new Uint8Array([0x89, 0x50, 0x4e, 0x47])) + const patch = yield* snapshot.patch(before) expect(patch.files).toContain(fwd(tmp.path, "image.png")) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - expect( - await fs - .access(`${tmp.path}/image.png`) - .then(() => true) - .catch(() => false), - ).toBe(false) - }, - }) -}) - -test("symlink handling", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await fs.symlink(`${tmp.path}/a.txt`, `${tmp.path}/link.txt`, "file") - - expect((await run(tmp.path, (snapshot) => snapshot.patch(before!))).files).toContain(fwd(tmp.path, "link.txt")) - }, - }) -}) - -test("file under size limit handling", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/large.txt`, "x".repeat(1024 * 1024)) - - expect((await run(tmp.path, (snapshot) => snapshot.patch(before!))).files).toContain(fwd(tmp.path, "large.txt")) - }, - }) -}) - -test("large added files are skipped", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/huge.txt`, new Uint8Array(2 * 1024 * 1024 + 1)) - - expect((await run(tmp.path, (snapshot) => snapshot.patch(before!))).files).toEqual([]) - expect(await run(tmp.path, (snapshot) => snapshot.diff(before!))).toBe("") - expect(await run(tmp.path, (snapshot) => snapshot.track())).toBe(before) - }, - }) -}) - -test("nested directory revert", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await $`mkdir -p ${tmp.path}/level1/level2/level3`.quiet() - await Filesystem.write(`${tmp.path}/level1/level2/level3/deep.txt`, "DEEP") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - expect( - await fs - .access(`${tmp.path}/level1/level2/level3/deep.txt`) - .then(() => true) - .catch(() => false), - ).toBe(false) - }, - }) -}) - -test("special characters in filenames", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/file with spaces.txt`, "SPACES") - await Filesystem.write(`${tmp.path}/file-with-dashes.txt`, "DASHES") - await Filesystem.write(`${tmp.path}/file_with_underscores.txt`, "UNDERSCORES") - - const files = (await run(tmp.path, (snapshot) => snapshot.patch(before!))).files + yield* snapshot.revert([patch]) + expect(yield* exists(`${tmp.path}/image.png`)).toBe(false) + }), + ), + { git: true }, +) + +it.instance( + "symlink handling", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* Effect.promise(() => fs.symlink(`${tmp.path}/a.txt`, `${tmp.path}/link.txt`, "file")) + expect((yield* snapshot.patch(before)).files).toContain(fwd(tmp.path, "link.txt")) + }), + ), + { git: true }, +) + +it.instance( + "file under size limit handling", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/large.txt`, "x".repeat(1024 * 1024)) + expect((yield* snapshot.patch(before)).files).toContain(fwd(tmp.path, "large.txt")) + }), + ), + { git: true }, +) + +it.instance( + "large added files are skipped", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/huge.txt`, new Uint8Array(2 * 1024 * 1024 + 1)) + expect((yield* snapshot.patch(before)).files).toEqual([]) + expect(yield* snapshot.diff(before)).toBe("") + expect(yield* snapshot.track()).toBe(before) + }), + ), + { git: true }, +) + +it.instance( + "nested directory revert", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* mkdirp(`${tmp.path}/level1/level2/level3`) + yield* write(`${tmp.path}/level1/level2/level3/deep.txt`, "DEEP") + const patch = yield* snapshot.patch(before) + yield* snapshot.revert([patch]) + expect(yield* exists(`${tmp.path}/level1/level2/level3/deep.txt`)).toBe(false) + }), + ), + { git: true }, +) + +it.instance( + "special characters in filenames", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/file with spaces.txt`, "SPACES") + yield* write(`${tmp.path}/file-with-dashes.txt`, "DASHES") + yield* write(`${tmp.path}/file_with_underscores.txt`, "UNDERSCORES") + const files = (yield* snapshot.patch(before)).files expect(files).toContain(fwd(tmp.path, "file with spaces.txt")) expect(files).toContain(fwd(tmp.path, "file-with-dashes.txt")) expect(files).toContain(fwd(tmp.path, "file_with_underscores.txt")) - }, - }) -}) - -test("revert with empty patches", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - // Should not crash with empty patches - expect(run(tmp.path, (snapshot) => snapshot.revert([]))).resolves.toBeUndefined() - - // Should not crash with patches that have empty file lists - expect(run(tmp.path, (snapshot) => snapshot.revert([{ hash: "dummy", files: [] }]))).resolves.toBeUndefined() - }, - }) -}) - -test("patch with invalid hash", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Create a change - await Filesystem.write(`${tmp.path}/test.txt`, "TEST") - - // Try to patch with invalid hash - should handle gracefully - const patch = await run(tmp.path, (snapshot) => snapshot.patch("invalid-hash-12345")) + }), + ), + { git: true }, +) + +it.instance( + "revert with empty patches", + Effect.gen(function* () { + yield* bootstrap() + const snapshot = yield* Snapshot.Service + yield* snapshot.revert([]) + yield* snapshot.revert([{ hash: "dummy", files: [] }]) + }), + { git: true }, +) + +it.instance( + "patch with invalid hash", + withTrackedSnapshot(({ tmp, snapshot }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/test.txt`, "TEST") + const patch = yield* snapshot.patch("invalid-hash-12345") expect(patch.files).toEqual([]) expect(patch.hash).toBe("invalid-hash-12345") - }, - }) -}) - -test("revert non-existent file", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Try to revert a file that doesn't exist in the snapshot - // This should not crash - expect( - run(tmp.path, (snapshot) => - snapshot.revert([ - { - hash: before!, - files: [`${tmp.path}/nonexistent.txt`], - }, - ]), - ), - ).resolves.toBeUndefined() - }, - }) -}) - -test("unicode filenames", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - + }), + ), + { git: true }, +) + +it.instance( + "revert non-existent file", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* snapshot.revert([{ hash: before, files: [`${tmp.path}/nonexistent.txt`] }]) + }), + ), + { git: true }, +) + +it.instance( + "unicode filenames", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { const unicodeFiles = [ { path: fwd(tmp.path, "文件.txt"), content: "chinese content" }, { path: fwd(tmp.path, "🚀rocket.txt"), content: "emoji content" }, { path: fwd(tmp.path, "café.txt"), content: "accented content" }, { path: fwd(tmp.path, "файл.txt"), content: "cyrillic content" }, ] - - for (const file of unicodeFiles) { - await Filesystem.write(file.path, file.content) - } - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) + yield* Effect.all( + unicodeFiles.map((file) => write(file.path, file.content)), + { concurrency: "unbounded" }, + ) + const patch = yield* snapshot.patch(before) expect(patch.files.length).toBe(4) - - for (const file of unicodeFiles) { - expect(patch.files).toContain(file.path) - } - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - for (const file of unicodeFiles) { - expect( - await fs - .access(file.path) - .then(() => true) - .catch(() => false), - ).toBe(false) - } - }, - }) -}) - -test.skip("unicode filenames modification and restore", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const chineseFile = fwd(tmp.path, "文件.txt") - const cyrillicFile = fwd(tmp.path, "файл.txt") - - await Filesystem.write(chineseFile, "original chinese") - await Filesystem.write(cyrillicFile, "original cyrillic") - - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(chineseFile, "modified chinese") - await Filesystem.write(cyrillicFile, "modified cyrillic") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - expect(patch.files).toContain(chineseFile) - expect(patch.files).toContain(cyrillicFile) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - expect(await fs.readFile(chineseFile, "utf-8")).toBe("original chinese") - expect(await fs.readFile(cyrillicFile, "utf-8")).toBe("original cyrillic") - }, - }) -}) - -test("unicode filenames in subdirectories", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await $`mkdir -p "${tmp.path}/目录/подкаталог"`.quiet() + for (const file of unicodeFiles) expect(patch.files).toContain(file.path) + yield* snapshot.revert([patch]) + for (const file of unicodeFiles) expect(yield* exists(file.path)).toBe(false) + }), + ), + { git: true }, +) + +it.instance.skip( + "unicode filenames modification and restore", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + const chineseFile = fwd(tmp.path, "文件.txt") + const cyrillicFile = fwd(tmp.path, "файл.txt") + yield* write(chineseFile, "original chinese") + yield* write(cyrillicFile, "original cyrillic") + const before = yield* snapshot.track() + expect(before).toBeTruthy() + yield* write(chineseFile, "modified chinese") + yield* write(cyrillicFile, "modified cyrillic") + const patch = yield* snapshot.patch(before!) + expect(patch.files).toContain(chineseFile) + expect(patch.files).toContain(cyrillicFile) + yield* snapshot.revert([patch]) + expect(yield* readText(chineseFile)).toBe("original chinese") + expect(yield* readText(cyrillicFile)).toBe("original cyrillic") + }), + { git: true }, +) + +it.instance( + "unicode filenames in subdirectories", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* mkdirp(`${tmp.path}/目录/подкаталог`) const deepFile = fwd(tmp.path, "目录", "подкаталог", "文件.txt") - await Filesystem.write(deepFile, "deep unicode content") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) + yield* write(deepFile, "deep unicode content") + const patch = yield* snapshot.patch(before) expect(patch.files).toContain(deepFile) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - expect( - await fs - .access(deepFile) - .then(() => true) - .catch(() => false), - ).toBe(false) - }, - }) -}) - -test("very long filenames", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - const longName = "a".repeat(200) + ".txt" - const longFile = fwd(tmp.path, longName) - - await Filesystem.write(longFile, "long filename content") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) + yield* snapshot.revert([patch]) + expect(yield* exists(deepFile)).toBe(false) + }), + ), + { git: true }, +) + +it.instance( + "very long filenames", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + const longFile = fwd(tmp.path, `${"a".repeat(200)}.txt`) + yield* write(longFile, "long filename content") + const patch = yield* snapshot.patch(before) expect(patch.files).toContain(longFile) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - expect( - await fs - .access(longFile) - .then(() => true) - .catch(() => false), - ).toBe(false) - }, - }) -}) - -test("hidden files", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/.hidden`, "hidden content") - await Filesystem.write(`${tmp.path}/.gitignore`, "*.log") - await Filesystem.write(`${tmp.path}/.config`, "config content") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) + yield* snapshot.revert([patch]) + expect(yield* exists(longFile)).toBe(false) + }), + ), + { git: true }, +) + +it.instance( + "hidden files", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/.hidden`, "hidden content") + yield* write(`${tmp.path}/.gitignore`, "*.log") + yield* write(`${tmp.path}/.config`, "config content") + const patch = yield* snapshot.patch(before) expect(patch.files).toContain(fwd(tmp.path, ".hidden")) expect(patch.files).toContain(fwd(tmp.path, ".gitignore")) expect(patch.files).toContain(fwd(tmp.path, ".config")) - }, - }) -}) - -test("nested symlinks", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await $`mkdir -p ${tmp.path}/sub/dir`.quiet() - await Filesystem.write(`${tmp.path}/sub/dir/target.txt`, "target content") - await fs.symlink(`${tmp.path}/sub/dir/target.txt`, `${tmp.path}/sub/dir/link.txt`, "file") - await fs.symlink(`${tmp.path}/sub`, `${tmp.path}/sub-link`, "dir") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) + }), + ), + { git: true }, +) + +it.instance( + "nested symlinks", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* mkdirp(`${tmp.path}/sub/dir`) + yield* write(`${tmp.path}/sub/dir/target.txt`, "target content") + yield* Effect.promise(() => fs.symlink(`${tmp.path}/sub/dir/target.txt`, `${tmp.path}/sub/dir/link.txt`, "file")) + yield* Effect.promise(() => fs.symlink(`${tmp.path}/sub`, `${tmp.path}/sub-link`, "dir")) + const patch = yield* snapshot.patch(before) expect(patch.files).toContain(fwd(tmp.path, "sub", "dir", "link.txt")) expect(patch.files).toContain(fwd(tmp.path, "sub-link")) - }, - }) -}) - -test("file permissions and ownership changes", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Change permissions multiple times - await $`chmod 600 ${tmp.path}/a.txt`.quiet() - await $`chmod 755 ${tmp.path}/a.txt`.quiet() - await $`chmod 644 ${tmp.path}/a.txt`.quiet() - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - // Note: git doesn't track permission changes on existing files by default - // Only tracks executable bit when files are first added - expect(patch.files.length).toBe(0) - }, - }) -}) - -test("circular symlinks", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Create circular symlink - await fs.symlink(`${tmp.path}/circular`, `${tmp.path}/circular`, "dir").catch(() => {}) - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - expect(patch.files.length).toBeGreaterThanOrEqual(0) // Should not crash - }, - }) -}) - -test("source project gitignore is respected - ignored files are not snapshotted", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - // Create gitignore BEFORE any tracking - await Filesystem.write(`${dir}/.gitignore`, "*.ignored\nbuild/\nnode_modules/\n") - await Filesystem.write(`${dir}/tracked.txt`, "tracked content") - await Filesystem.write(`${dir}/ignored.ignored`, "ignored content") - await $`mkdir -p ${dir}/build`.quiet() - await Filesystem.write(`${dir}/build/output.js`, "build output") - await Filesystem.write(`${dir}/normal.js`, "normal js") - await $`git add .`.cwd(dir).quiet() - await $`git commit -m init`.cwd(dir).quiet() - }, - }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Modify tracked files and create new ones - some ignored, some not - await Filesystem.write(`${tmp.path}/tracked.txt`, "modified tracked") - await Filesystem.write(`${tmp.path}/new.ignored`, "new ignored") - await Filesystem.write(`${tmp.path}/new-tracked.txt`, "new tracked") - await Filesystem.write(`${tmp.path}/build/new-build.js`, "new build file") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - - // Modified and new tracked files should be in snapshot - expect(patch.files).toContain(fwd(tmp.path, "new-tracked.txt")) - expect(patch.files).toContain(fwd(tmp.path, "tracked.txt")) - - // Ignored files should NOT be in snapshot - expect(patch.files).not.toContain(fwd(tmp.path, "new.ignored")) - expect(patch.files).not.toContain(fwd(tmp.path, "ignored.ignored")) - expect(patch.files).not.toContain(fwd(tmp.path, "build/output.js")) - expect(patch.files).not.toContain(fwd(tmp.path, "build/new-build.js")) - }, - }) -}) - -test("gitignore changes", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) + }), + ), + { git: true }, +) + +it.instance( + "file permissions and ownership changes", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* Effect.promise(() => fs.chmod(`${tmp.path}/a.txt`, 0o600)) + yield* Effect.promise(() => fs.chmod(`${tmp.path}/a.txt`, 0o755)) + yield* Effect.promise(() => fs.chmod(`${tmp.path}/a.txt`, 0o644)) + expect((yield* snapshot.patch(before)).files.length).toBe(0) + }), + ), + { git: true }, +) + +it.instance( + "circular symlinks", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* Effect.promise(() => + fs.symlink(`${tmp.path}/circular`, `${tmp.path}/circular`, "dir").catch(() => undefined), + ) + expect((yield* snapshot.patch(before)).files.length).toBeGreaterThanOrEqual(0) + }), + ), + { git: true }, +) + +it.live( + "source project gitignore is respected - ignored files are not snapshotted", + Effect.gen(function* () { + const dir = yield* scopedGitTmpdir() + yield* write(`${dir}/.gitignore`, "*.ignored\nbuild/\nnode_modules/\n") + yield* write(`${dir}/tracked.txt`, "tracked content") + yield* write(`${dir}/ignored.ignored`, "ignored content") + yield* mkdirp(`${dir}/build`) + yield* write(`${dir}/build/output.js`, "build output") + yield* write(`${dir}/normal.js`, "normal js") + yield* exec(dir, ["git", "add", "."]) + yield* exec(dir, ["git", "commit", "-m", "init"]) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.track() expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/.gitignore`, "*.ignored") - await Filesystem.write(`${tmp.path}/test.ignored`, "ignored content") - await Filesystem.write(`${tmp.path}/normal.txt`, "normal content") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - - // Should track gitignore itself + yield* write(`${dir}/tracked.txt`, "modified tracked") + yield* write(`${dir}/new.ignored`, "new ignored") + yield* write(`${dir}/new-tracked.txt`, "new tracked") + yield* write(`${dir}/build/new-build.js`, "new build file") + const patch = yield* snapshot.patch(before!) + expect(patch.files).toContain(fwd(dir, "new-tracked.txt")) + expect(patch.files).toContain(fwd(dir, "tracked.txt")) + expect(patch.files).not.toContain(fwd(dir, "new.ignored")) + expect(patch.files).not.toContain(fwd(dir, "ignored.ignored")) + expect(patch.files).not.toContain(fwd(dir, "build/output.js")) + expect(patch.files).not.toContain(fwd(dir, "build/new-build.js")) + }).pipe(provideInstance(dir)) + }), +) + +it.instance( + "gitignore changes", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/.gitignore`, "*.ignored") + yield* write(`${tmp.path}/test.ignored`, "ignored content") + yield* write(`${tmp.path}/normal.txt`, "normal content") + const patch = yield* snapshot.patch(before) expect(patch.files).toContain(fwd(tmp.path, ".gitignore")) - // Should track normal files expect(patch.files).toContain(fwd(tmp.path, "normal.txt")) - // Should not track ignored files (git won't see them) expect(patch.files).not.toContain(fwd(tmp.path, "test.ignored")) - }, - }) -}) - -test("files tracked in snapshot but now gitignored are filtered out", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - // First, create a file and snapshot it - await Filesystem.write(`${tmp.path}/later-ignored.txt`, "initial content") - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Modify the file (so it appears in diff-files) - await Filesystem.write(`${tmp.path}/later-ignored.txt`, "modified content") - - // Now add gitignore that would exclude this file - await Filesystem.write(`${tmp.path}/.gitignore`, "later-ignored.txt\n") - - // Also create another tracked file - await Filesystem.write(`${tmp.path}/still-tracked.txt`, "new tracked file") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) - - // The file that is now gitignored should NOT appear, even though it was - // previously tracked and modified - expect(patch.files).not.toContain(fwd(tmp.path, "later-ignored.txt")) - - // The gitignore file itself should appear - expect(patch.files).toContain(fwd(tmp.path, ".gitignore")) - - // Other tracked files should appear - expect(patch.files).toContain(fwd(tmp.path, "still-tracked.txt")) - }, - }) -}) - -test("gitignore updated between track calls filters from diff", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - // a.txt is already committed from bootstrap - track it in snapshot - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Modify a.txt (so it appears in diff-files) - await Filesystem.write(`${tmp.path}/a.txt`, "modified content") - - // Now add gitignore that would exclude a.txt - await Filesystem.write(`${tmp.path}/.gitignore`, "a.txt\n") - - // Also modify b.txt which is not gitignored - await Filesystem.write(`${tmp.path}/b.txt`, "also modified") - - // Second track - should not include a.txt even though it changed - const after = await run(tmp.path, (snapshot) => snapshot.track()) + }), + ), + { git: true }, +) + +it.instance( + "files tracked in snapshot but now gitignored are filtered out", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + yield* write(`${tmp.path}/later-ignored.txt`, "initial content") + const before = yield* snapshot.track() + expect(before).toBeTruthy() + yield* write(`${tmp.path}/later-ignored.txt`, "modified content") + yield* write(`${tmp.path}/.gitignore`, "later-ignored.txt\n") + yield* write(`${tmp.path}/still-tracked.txt`, "new tracked file") + const patch = yield* snapshot.patch(before!) + expect(patch.files).not.toContain(fwd(tmp.path, "later-ignored.txt")) + expect(patch.files).toContain(fwd(tmp.path, ".gitignore")) + expect(patch.files).toContain(fwd(tmp.path, "still-tracked.txt")) + }), + { git: true }, +) + +it.instance( + "gitignore updated between track calls filters from diff", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/a.txt`, "modified content") + yield* write(`${tmp.path}/.gitignore`, "a.txt\n") + yield* write(`${tmp.path}/b.txt`, "also modified") + const after = yield* snapshot.track() expect(after).toBeTruthy() - - // Verify a.txt is NOT in the diff between snapshots - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.some((x) => x.file === "a.txt")).toBe(false) - - // But .gitignore should be in the diff expect(diffs.some((x) => x.file === ".gitignore")).toBe(true) - - // b.txt should be in the diff (not gitignored) expect(diffs.some((x) => x.file === "b.txt")).toBe(true) - }, - }) -}) - -test("git info exclude changes", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - + }), + ), + { git: true }, +) + +it.instance( + "git info exclude changes", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { const file = `${tmp.path}/.git/info/exclude` - const text = await Bun.file(file).text() - await Bun.write(file, `${text.trimEnd()}\nignored.txt\n`) - await Bun.write(`${tmp.path}/ignored.txt`, "ignored content") - await Bun.write(`${tmp.path}/normal.txt`, "normal content") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) + yield* write(file, `${(yield* Effect.promise(() => Bun.file(file).text())).trimEnd()}\nignored.txt\n`) + yield* write(`${tmp.path}/ignored.txt`, "ignored content") + yield* write(`${tmp.path}/normal.txt`, "normal content") + const patch = yield* snapshot.patch(before) expect(patch.files).toContain(fwd(tmp.path, "normal.txt")) expect(patch.files).not.toContain(fwd(tmp.path, "ignored.txt")) - - const after = await run(tmp.path, (snapshot) => snapshot.track()) - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const after = yield* snapshot.track() + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.some((x) => x.file === "normal.txt")).toBe(true) expect(diffs.some((x) => x.file === "ignored.txt")).toBe(false) - }, - }) -}) - -test("git info exclude keeps global excludes", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const global = `${tmp.path}/global.ignore` - const config = `${tmp.path}/global.gitconfig` - await Bun.write(global, "global.tmp\n") - await Bun.write(config, `[core]\n\texcludesFile = ${global.replaceAll("\\", "/")}\n`) - - const prev = process.env.GIT_CONFIG_GLOBAL - process.env.GIT_CONFIG_GLOBAL = config - try { - const before = await run(tmp.path, (snapshot) => snapshot.track()) + }), + ), + { git: true }, +) + +it.instance( + "git info exclude keeps global excludes", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const global = `${tmp.path}/global.ignore` + const config = `${tmp.path}/global.gitconfig` + yield* write(global, "global.tmp\n") + yield* write(config, `[core]\n\texcludesFile = ${global.replaceAll("\\", "/")}\n`) + yield* withGitConfigGlobal( + config, + Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.track() expect(before).toBeTruthy() - const file = `${tmp.path}/.git/info/exclude` - const text = await Bun.file(file).text() - await Bun.write(file, `${text.trimEnd()}\ninfo.tmp\n`) - - await Bun.write(`${tmp.path}/global.tmp`, "global content") - await Bun.write(`${tmp.path}/info.tmp`, "info content") - await Bun.write(`${tmp.path}/normal.txt`, "normal content") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(before!)) + yield* write(file, `${(yield* Effect.promise(() => Bun.file(file).text())).trimEnd()}\ninfo.tmp\n`) + yield* write(`${tmp.path}/global.tmp`, "global content") + yield* write(`${tmp.path}/info.tmp`, "info content") + yield* write(`${tmp.path}/normal.txt`, "normal content") + const patch = yield* snapshot.patch(before!) expect(patch.files).toContain(fwd(tmp.path, "normal.txt")) expect(patch.files).not.toContain(fwd(tmp.path, "global.tmp")) expect(patch.files).not.toContain(fwd(tmp.path, "info.tmp")) - } finally { - if (prev) process.env.GIT_CONFIG_GLOBAL = prev - else delete process.env.GIT_CONFIG_GLOBAL - } - }, - }) -}) - -test("concurrent file operations during patch", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Start creating files - const createPromise = (async () => { + }), + ) + }), + { git: true }, +) + +it.instance( + "concurrent file operations during patch", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + const fiber = yield* Effect.gen(function* () { for (let i = 0; i < 10; i++) { - await Filesystem.write(`${tmp.path}/concurrent${i}.txt`, `concurrent${i}`) - // Small delay to simulate concurrent operations - await new Promise((resolve) => setTimeout(resolve, 1)) + yield* write(`${tmp.path}/concurrent${i}.txt`, `concurrent${i}`) + yield* Effect.sleep("1 millis") } - })() - - // Get patch while files are being created - const patchPromise = run(tmp.path, (snapshot) => snapshot.patch(before!)) - - await createPromise - const patch = await patchPromise - - // Should capture some or all of the concurrent files + }).pipe(Effect.forkScoped) + const patch = yield* snapshot.patch(before) + yield* Fiber.join(fiber) expect(patch.files.length).toBeGreaterThanOrEqual(0) - }, - }) -}) - -test("snapshot state isolation between projects", async () => { - // Test that different projects don't interfere with each other - await using tmp1 = await bootstrap() - await using tmp2 = await bootstrap() - - await WithInstance.provide({ - directory: tmp1.path, - fn: async () => { - const before1 = await run(tmp1.path, (snapshot) => snapshot.track()) - await Filesystem.write(`${tmp1.path}/project1.txt`, "project1 content") - const patch1 = await run(tmp1.path, (snapshot) => snapshot.patch(before1!)) + }), + ), + { git: true }, +) + +it.live( + "snapshot state isolation between projects", + Effect.gen(function* () { + const tmp1 = yield* bootstrapScoped() + const tmp2 = yield* bootstrapScoped() + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before1 = yield* snapshot.track() + yield* write(`${tmp1.path}/project1.txt`, "project1 content") + const patch1 = yield* snapshot.patch(before1!) expect(patch1.files).toContain(fwd(tmp1.path, "project1.txt")) - }, - }) - - await WithInstance.provide({ - directory: tmp2.path, - fn: async () => { - const before2 = await run(tmp2.path, (snapshot) => snapshot.track()) - await Filesystem.write(`${tmp2.path}/project2.txt`, "project2 content") - const patch2 = await run(tmp2.path, (snapshot) => snapshot.patch(before2!)) + }).pipe(provideInstance(tmp1.path)) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before2 = yield* snapshot.track() + yield* write(`${tmp2.path}/project2.txt`, "project2 content") + const patch2 = yield* snapshot.patch(before2!) expect(patch2.files).toContain(fwd(tmp2.path, "project2.txt")) - - // Ensure project1 files don't appear in project2 - expect(patch2.files).not.toContain(fwd(tmp1?.path ?? "", "project1.txt")) - }, - }) -}) - -test("patch detects changes in secondary worktree", async () => { - await using tmp = await bootstrap() - const worktreePath = `${tmp.path}-worktree` - await $`git worktree add ${worktreePath} HEAD`.cwd(tmp.path).quiet() - - try { - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - expect(await run(tmp.path, (snapshot) => snapshot.track())).toBeTruthy() - }, - }) - - await WithInstance.provide({ - directory: worktreePath, - fn: async () => { - const before = await run(worktreePath, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - const worktreeFile = fwd(worktreePath, "worktree.txt") - await Filesystem.write(worktreeFile, "worktree content") - - const patch = await run(worktreePath, (snapshot) => snapshot.patch(before!)) - expect(patch.files).toContain(worktreeFile) - }, - }) - } finally { - await $`git worktree remove --force ${worktreePath}`.cwd(tmp.path).quiet().nothrow() - await $`rm -rf ${worktreePath}`.quiet() - } -}) - -test("revert only removes files in invoking worktree", async () => { - await using tmp = await bootstrap() - const worktreePath = `${tmp.path}-worktree` - await $`git worktree add ${worktreePath} HEAD`.cwd(tmp.path).quiet() - - try { - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - expect(await run(tmp.path, (snapshot) => snapshot.track())).toBeTruthy() - }, - }) + expect(patch2.files).not.toContain(fwd(tmp1.path, "project1.txt")) + }).pipe(provideInstance(tmp2.path)) + }), +) + +it.live( + "patch detects changes in secondary worktree", + Effect.gen(function* () { + const tmp = yield* bootstrapScoped() + const worktreePath = `${tmp.path}-worktree` + yield* exec(tmp.path, ["git", "worktree", "add", worktreePath, "HEAD"]) + yield* Effect.addFinalizer(() => cleanupWorktree(tmp.path, worktreePath)) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + expect(yield* snapshot.track()).toBeTruthy() + }).pipe(provideInstance(tmp.path)) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.track() + expect(before).toBeTruthy() + const worktreeFile = fwd(worktreePath, "worktree.txt") + yield* write(worktreeFile, "worktree content") + expect((yield* snapshot.patch(before!)).files).toContain(worktreeFile) + }).pipe(provideInstance(worktreePath)) + }), +) + +it.live( + "revert only removes files in invoking worktree", + Effect.gen(function* () { + const tmp = yield* bootstrapScoped() + const worktreePath = `${tmp.path}-worktree` const primaryFile = `${tmp.path}/worktree.txt` - await Filesystem.write(primaryFile, "primary content") - - await WithInstance.provide({ - directory: worktreePath, - fn: async () => { - const before = await run(worktreePath, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - const worktreeFile = fwd(worktreePath, "worktree.txt") - await Filesystem.write(worktreeFile, "worktree content") - - const patch = await run(worktreePath, (snapshot) => snapshot.patch(before!)) - await run(worktreePath, (snapshot) => snapshot.revert([patch])) - - expect( - await fs - .access(worktreeFile) - .then(() => true) - .catch(() => false), - ).toBe(false) - }, - }) - - expect(await fs.readFile(primaryFile, "utf-8")).toBe("primary content") - } finally { - await $`git worktree remove --force ${worktreePath}`.cwd(tmp.path).quiet().nothrow() - await $`rm -rf ${worktreePath}`.quiet() - await $`rm -f ${tmp.path}/worktree.txt`.quiet() - } -}) - -test("diff reports worktree-only/shared edits and ignores primary-only", async () => { - await using tmp = await bootstrap() - const worktreePath = `${tmp.path}-worktree` - await $`git worktree add ${worktreePath} HEAD`.cwd(tmp.path).quiet() - - try { - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - expect(await run(tmp.path, (snapshot) => snapshot.track())).toBeTruthy() - }, - }) - - await WithInstance.provide({ - directory: worktreePath, - fn: async () => { - const before = await run(worktreePath, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${worktreePath}/worktree-only.txt`, "worktree diff content") - await Filesystem.write(`${worktreePath}/shared.txt`, "worktree edit") - await Filesystem.write(`${tmp.path}/shared.txt`, "primary edit") - await Filesystem.write(`${tmp.path}/primary-only.txt`, "primary change") - - const diff = await run(worktreePath, (snapshot) => snapshot.diff(before!)) - expect(diff).toContain("worktree-only.txt") - expect(diff).toContain("shared.txt") - expect(diff).not.toContain("primary-only.txt") - }, - }) - } finally { - await $`git worktree remove --force ${worktreePath}`.cwd(tmp.path).quiet().nothrow() - await $`rm -rf ${worktreePath}`.quiet() - await $`rm -f ${tmp.path}/shared.txt`.quiet() - await $`rm -f ${tmp.path}/primary-only.txt`.quiet() - } -}) - -test("track with no changes returns same hash", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const hash1 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(hash1).toBeTruthy() - - // Track again with no changes - const hash2 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(hash2).toBe(hash1!) - - // Track again - const hash3 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(hash3).toBe(hash1!) - }, - }) -}) - -test("diff function with various changes", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) + yield* exec(tmp.path, ["git", "worktree", "add", worktreePath, "HEAD"]) + yield* Effect.addFinalizer(() => cleanupWorktree(tmp.path, worktreePath, [primaryFile])) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + expect(yield* snapshot.track()).toBeTruthy() + }).pipe(provideInstance(tmp.path)) + yield* write(primaryFile, "primary content") + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.track() expect(before).toBeTruthy() - - // Make various changes - await $`rm ${tmp.path}/a.txt`.quiet() - await Filesystem.write(`${tmp.path}/new.txt`, "new content") - await Filesystem.write(`${tmp.path}/b.txt`, "modified content") - - const diff = await run(tmp.path, (snapshot) => snapshot.diff(before!)) + const worktreeFile = fwd(worktreePath, "worktree.txt") + yield* write(worktreeFile, "worktree content") + const patch = yield* snapshot.patch(before!) + yield* snapshot.revert([patch]) + expect(yield* exists(worktreeFile)).toBe(false) + }).pipe(provideInstance(worktreePath)) + expect(yield* readText(primaryFile)).toBe("primary content") + }), +) + +it.live( + "diff reports worktree-only/shared edits and ignores primary-only", + Effect.gen(function* () { + const tmp = yield* bootstrapScoped() + const worktreePath = `${tmp.path}-worktree` + yield* exec(tmp.path, ["git", "worktree", "add", worktreePath, "HEAD"]) + yield* Effect.addFinalizer(() => + cleanupWorktree(tmp.path, worktreePath, [`${tmp.path}/shared.txt`, `${tmp.path}/primary-only.txt`]), + ) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + expect(yield* snapshot.track()).toBeTruthy() + }).pipe(provideInstance(tmp.path)) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.track() + expect(before).toBeTruthy() + yield* write(`${worktreePath}/worktree-only.txt`, "worktree diff content") + yield* write(`${worktreePath}/shared.txt`, "worktree edit") + yield* write(`${tmp.path}/shared.txt`, "primary edit") + yield* write(`${tmp.path}/primary-only.txt`, "primary change") + const diff = yield* snapshot.diff(before!) + expect(diff).toContain("worktree-only.txt") + expect(diff).toContain("shared.txt") + expect(diff).not.toContain("primary-only.txt") + }).pipe(provideInstance(worktreePath)) + }), +) + +it.instance( + "track with no changes returns same hash", + withTrackedSnapshot(({ snapshot, before }) => + Effect.gen(function* () { + expect(yield* snapshot.track()).toBe(before) + expect(yield* snapshot.track()).toBe(before) + }), + ), + { git: true }, +) + +it.instance( + "diff function with various changes", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* rm(`${tmp.path}/a.txt`) + yield* write(`${tmp.path}/new.txt`, "new content") + yield* write(`${tmp.path}/b.txt`, "modified content") + const diff = yield* snapshot.diff(before) expect(diff).toContain("a.txt") expect(diff).toContain("b.txt") expect(diff).toContain("new.txt") - }, - }) -}) - -test("restore function", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - // Make changes - await $`rm ${tmp.path}/a.txt`.quiet() - await Filesystem.write(`${tmp.path}/new.txt`, "new content") - await Filesystem.write(`${tmp.path}/b.txt`, "modified") - - // Restore to original state - await run(tmp.path, (snapshot) => snapshot.restore(before!)) - - expect( - await fs - .access(`${tmp.path}/a.txt`) - .then(() => true) - .catch(() => false), - ).toBe(true) - expect(await fs.readFile(`${tmp.path}/a.txt`, "utf-8")).toBe(tmp.extra.aContent) - expect( - await fs - .access(`${tmp.path}/new.txt`) - .then(() => true) - .catch(() => false), - ).toBe(true) // New files should remain - expect(await fs.readFile(`${tmp.path}/b.txt`, "utf-8")).toBe(tmp.extra.bContent) - }, - }) -}) - -test("revert should not delete files that existed but were deleted in snapshot", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const snapshot1 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(snapshot1).toBeTruthy() - - await $`rm ${tmp.path}/a.txt`.quiet() - - const snapshot2 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(snapshot2).toBeTruthy() - - await Filesystem.write(`${tmp.path}/a.txt`, "recreated content") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(snapshot2!)) - expect(patch.files).toContain(fwd(tmp.path, "a.txt")) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - expect( - await fs - .access(`${tmp.path}/a.txt`) - .then(() => true) - .catch(() => false), - ).toBe(false) - }, - }) -}) - -test("revert preserves file that existed in snapshot when deleted then recreated", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await Filesystem.write(`${tmp.path}/existing.txt`, "original content") - - const hash = await run(tmp.path, (snapshot) => snapshot.track()) - expect(hash).toBeTruthy() - - await $`rm ${tmp.path}/existing.txt`.quiet() - await Filesystem.write(`${tmp.path}/existing.txt`, "recreated") - await Filesystem.write(`${tmp.path}/newfile.txt`, "new") - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(hash!)) - expect(patch.files).toContain(fwd(tmp.path, "existing.txt")) - expect(patch.files).toContain(fwd(tmp.path, "newfile.txt")) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - expect( - await fs - .access(`${tmp.path}/newfile.txt`) - .then(() => true) - .catch(() => false), - ).toBe(false) - expect( - await fs - .access(`${tmp.path}/existing.txt`) - .then(() => true) - .catch(() => false), - ).toBe(true) - expect(await fs.readFile(`${tmp.path}/existing.txt`, "utf-8")).toBe("original content") - }, - }) -}) - -test("diffFull sets status based on git change type", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await Filesystem.write(`${tmp.path}/grow.txt`, "one\n") - await Filesystem.write(`${tmp.path}/trim.txt`, "line1\nline2\n") - await Filesystem.write(`${tmp.path}/delete.txt`, "gone") - - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/grow.txt`, "one\ntwo\n") - await Filesystem.write(`${tmp.path}/trim.txt`, "line1\n") - await $`rm ${tmp.path}/delete.txt`.quiet() - await Filesystem.write(`${tmp.path}/added.txt`, "new") - - const after = await run(tmp.path, (snapshot) => snapshot.track()) - expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) - expect(diffs.length).toBe(4) - - const added = diffs.find((d) => d.file === "added.txt") - expect(added).toBeDefined() - expect(added!.status).toBe("added") - - const deleted = diffs.find((d) => d.file === "delete.txt") - expect(deleted).toBeDefined() - expect(deleted!.status).toBe("deleted") - - const grow = diffs.find((d) => d.file === "grow.txt") - expect(grow).toBeDefined() - expect(grow!.status).toBe("modified") - expect(grow!.additions).toBeGreaterThan(0) - expect(grow!.deletions).toBe(0) - - const trim = diffs.find((d) => d.file === "trim.txt") - expect(trim).toBeDefined() - expect(trim!.status).toBe("modified") - expect(trim!.additions).toBe(0) - expect(trim!.deletions).toBeGreaterThan(0) - }, - }) -}) - -test("diffFull with new file additions", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/new.txt`, "new content") - - const after = await run(tmp.path, (snapshot) => snapshot.track()) + }), + ), + { git: true }, +) + +it.instance( + "restore function", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* rm(`${tmp.path}/a.txt`) + yield* write(`${tmp.path}/new.txt`, "new content") + yield* write(`${tmp.path}/b.txt`, "modified") + yield* snapshot.restore(before) + expect(yield* exists(`${tmp.path}/a.txt`)).toBe(true) + expect(yield* readText(`${tmp.path}/a.txt`)).toBe(tmp.extra.aContent) + expect(yield* exists(`${tmp.path}/new.txt`)).toBe(true) + expect(yield* readText(`${tmp.path}/b.txt`)).toBe(tmp.extra.bContent) + }), + ), + { git: true }, +) + +it.instance( + "revert should not delete files that existed but were deleted in snapshot", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + const snapshot1 = yield* snapshot.track() + expect(snapshot1).toBeTruthy() + yield* rm(`${tmp.path}/a.txt`) + const snapshot2 = yield* snapshot.track() + expect(snapshot2).toBeTruthy() + yield* write(`${tmp.path}/a.txt`, "recreated content") + const patch = yield* snapshot.patch(snapshot2!) + expect(patch.files).toContain(fwd(tmp.path, "a.txt")) + yield* snapshot.revert([patch]) + expect(yield* exists(`${tmp.path}/a.txt`)).toBe(false) + }), + { git: true }, +) + +it.instance( + "revert preserves file that existed in snapshot when deleted then recreated", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + yield* write(`${tmp.path}/existing.txt`, "original content") + const hash = yield* snapshot.track() + expect(hash).toBeTruthy() + yield* rm(`${tmp.path}/existing.txt`) + yield* write(`${tmp.path}/existing.txt`, "recreated") + yield* write(`${tmp.path}/newfile.txt`, "new") + const patch = yield* snapshot.patch(hash!) + expect(patch.files).toContain(fwd(tmp.path, "existing.txt")) + expect(patch.files).toContain(fwd(tmp.path, "newfile.txt")) + yield* snapshot.revert([patch]) + expect(yield* exists(`${tmp.path}/newfile.txt`)).toBe(false) + expect(yield* exists(`${tmp.path}/existing.txt`)).toBe(true) + expect(yield* readText(`${tmp.path}/existing.txt`)).toBe("original content") + }), + { git: true }, +) + +it.instance( + "diffFull sets status based on git change type", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + yield* write(`${tmp.path}/grow.txt`, "one\n") + yield* write(`${tmp.path}/trim.txt`, "line1\nline2\n") + yield* write(`${tmp.path}/delete.txt`, "gone") + const before = yield* snapshot.track() + expect(before).toBeTruthy() + yield* write(`${tmp.path}/grow.txt`, "one\ntwo\n") + yield* write(`${tmp.path}/trim.txt`, "line1\n") + yield* rm(`${tmp.path}/delete.txt`) + yield* write(`${tmp.path}/added.txt`, "new") + const after = yield* snapshot.track() + expect(after).toBeTruthy() + const diffs = yield* snapshot.diffFull(before!, after!) + expect(diffs.length).toBe(4) + expect(diffs.find((d) => d.file === "added.txt")!.status).toBe("added") + expect(diffs.find((d) => d.file === "delete.txt")!.status).toBe("deleted") + const grow = diffs.find((d) => d.file === "grow.txt")! + expect(grow.status).toBe("modified") + expect(grow.additions).toBeGreaterThan(0) + expect(grow.deletions).toBe(0) + const trim = diffs.find((d) => d.file === "trim.txt")! + expect(trim.status).toBe("modified") + expect(trim.additions).toBe(0) + expect(trim.deletions).toBeGreaterThan(0) + }), + { git: true }, +) + +it.instance( + "diffFull with new file additions", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/new.txt`, "new content") + const after = yield* snapshot.track() expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.length).toBe(1) - - const newFileDiff = diffs[0] - expect(newFileDiff.file).toBe("new.txt") - expect(newFileDiff.patch).toContain("+new content") - expect(newFileDiff.additions).toBe(1) - expect(newFileDiff.deletions).toBe(0) - }, - }) -}) - -test("diffFull with a large interleaved mixed diff", async () => { - await using tmp = await bootstrap() - await WithInstance.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 run(tmp.path, (snapshot) => 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 run(tmp.path, (snapshot) => snapshot.track()) - expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => 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!.patch).toContain(`-before-${ids[i]}-é`) - expect(m!.patch).toContain(`+after-${ids[i]}-é`) - expect(m!.status).toBe("modified") - - const d = map.get(fwd("mix", `${ids[i]}-del.txt`)) - expect(d).toBeDefined() - expect(d!.patch).toContain(`-gone-${ids[i]}`) - expect(d!.status).toBe("deleted") - - const a = map.get(fwd("mix", `${ids[i]}-add.txt`)) - expect(a).toBeDefined() - expect(a!.patch).toContain(`+new-${ids[i]}`) - expect(a!.status).toBe("added") - - const b = map.get(fwd("mix", `${ids[i]}-bin.bin`)) - expect(b).toBeDefined() - expect(b!.patch).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 WithInstance.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 run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Promise.all(ids.map((id) => Filesystem.write(`${tmp.path}/order/${id}.txt`, `after-${id}`))) - - const after = await run(tmp.path, (snapshot) => snapshot.track()) - expect(after).toBeTruthy() - - const expected = ids.map((id) => `order/${id}.txt`) - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) - expect(diffs.map((item) => item.file)).toEqual(expected) - }, - }) -}) - -test("diffFull with file modifications", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/b.txt`, "modified content") - - const after = await run(tmp.path, (snapshot) => snapshot.track()) + expect(diffs[0].file).toBe("new.txt") + expect(diffs[0].patch).toContain("+new content") + expect(diffs[0].additions).toBe(1) + expect(diffs[0].deletions).toBe(0) + }), + ), + { git: true }, +) + +it.instance( + "diffFull with a large interleaved mixed diff", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + 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`)) + yield* mkdirp(`${tmp.path}/mix`) + yield* Effect.all( + [ + ...mod.map((file, i) => write(file, `before-${ids[i]}-é\n🙂\nline`)), + ...del.map((file, i) => write(file, `gone-${ids[i]}\n你好`)), + ...bin.map((file, i) => write(file, new Uint8Array([0, i, 255, i % 251]))), + ], + { concurrency: "unbounded" }, + ) + const before = yield* snapshot.track() + expect(before).toBeTruthy() + yield* Effect.all( + [ + ...mod.map((file, i) => write(file, `after-${ids[i]}-é\n🚀\nline`)), + ...add.map((file, i) => write(file, `new-${ids[i]}\nこんにちは`)), + ...bin.map((file, i) => write(file, new Uint8Array([9, i, 8, i % 251]))), + ...del.map((file) => rm(file)), + ], + { concurrency: "unbounded" }, + ) + const after = yield* snapshot.track() + expect(after).toBeTruthy() + const diffs = yield* 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!.patch).toContain(`-before-${ids[i]}-é`) + expect(m!.patch).toContain(`+after-${ids[i]}-é`) + expect(m!.status).toBe("modified") + const d = map.get(fwd("mix", `${ids[i]}-del.txt`)) + expect(d).toBeDefined() + expect(d!.patch).toContain(`-gone-${ids[i]}`) + expect(d!.status).toBe("deleted") + const a = map.get(fwd("mix", `${ids[i]}-add.txt`)) + expect(a).toBeDefined() + expect(a!.patch).toContain(`+new-${ids[i]}`) + expect(a!.status).toBe("added") + const b = map.get(fwd("mix", `${ids[i]}-bin.bin`)) + expect(b).toBeDefined() + expect(b!.patch).toBe("") + expect(b!.additions).toBe(0) + expect(b!.deletions).toBe(0) + expect(b!.status).toBe("modified") + } + }), + { git: true }, +) + +it.instance( + "diffFull preserves git diff order across batch boundaries", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + const ids = Array.from({ length: 140 }, (_, i) => i.toString().padStart(3, "0")) + yield* mkdirp(`${tmp.path}/order`) + yield* Effect.all( + ids.map((id) => write(`${tmp.path}/order/${id}.txt`, `before-${id}`)), + { concurrency: "unbounded" }, + ) + const before = yield* snapshot.track() + expect(before).toBeTruthy() + yield* Effect.all( + ids.map((id) => write(`${tmp.path}/order/${id}.txt`, `after-${id}`)), + { concurrency: "unbounded" }, + ) + const after = yield* snapshot.track() + expect(after).toBeTruthy() + expect((yield* snapshot.diffFull(before!, after!)).map((item) => item.file)).toEqual( + ids.map((id) => `order/${id}.txt`), + ) + }), + { git: true }, +) + +it.instance( + "diffFull with file modifications", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/b.txt`, "modified content") + const after = yield* snapshot.track() expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.length).toBe(1) - - const modifiedFileDiff = diffs[0] - expect(modifiedFileDiff.file).toBe("b.txt") - expect(modifiedFileDiff.patch).toContain(`-${tmp.extra.bContent}`) - expect(modifiedFileDiff.patch).toContain("+modified content") - expect(modifiedFileDiff.additions).toBeGreaterThan(0) - expect(modifiedFileDiff.deletions).toBeGreaterThan(0) - }, - }) -}) - -test("diffFull with file deletions", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await $`rm ${tmp.path}/a.txt`.quiet() - - const after = await run(tmp.path, (snapshot) => snapshot.track()) + expect(diffs[0].file).toBe("b.txt") + expect(diffs[0].patch).toContain(`-${tmp.extra.bContent}`) + expect(diffs[0].patch).toContain("+modified content") + expect(diffs[0].additions).toBeGreaterThan(0) + expect(diffs[0].deletions).toBeGreaterThan(0) + }), + ), + { git: true }, +) + +it.instance( + "diffFull with file deletions", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* rm(`${tmp.path}/a.txt`) + const after = yield* snapshot.track() expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.length).toBe(1) - - const removedFileDiff = diffs[0] - expect(removedFileDiff.file).toBe("a.txt") - expect(removedFileDiff.patch).toContain(`-${tmp.extra.aContent}`) - expect(removedFileDiff.additions).toBe(0) - expect(removedFileDiff.deletions).toBe(1) - }, - }) -}) - -test("diffFull with multiple line additions", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/multi.txt`, "line1\nline2\nline3") - - const after = await run(tmp.path, (snapshot) => snapshot.track()) + expect(diffs[0].file).toBe("a.txt") + expect(diffs[0].patch).toContain(`-${tmp.extra.aContent}`) + expect(diffs[0].additions).toBe(0) + expect(diffs[0].deletions).toBe(1) + }), + ), + { git: true }, +) + +it.instance( + "diffFull with multiple line additions", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/multi.txt`, "line1\nline2\nline3") + const after = yield* snapshot.track() expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.length).toBe(1) - - const multiDiff = diffs[0] - expect(multiDiff.file).toBe("multi.txt") - expect(multiDiff.patch).toContain("+line1") - expect(multiDiff.patch).toContain("+line3") - expect(multiDiff.additions).toBe(3) - expect(multiDiff.deletions).toBe(0) - }, - }) -}) - -test("diffFull with addition and deletion", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/added.txt`, "added content") - await $`rm ${tmp.path}/a.txt`.quiet() - - const after = await run(tmp.path, (snapshot) => snapshot.track()) + expect(diffs[0].file).toBe("multi.txt") + expect(diffs[0].patch).toContain("+line1") + expect(diffs[0].patch).toContain("+line3") + expect(diffs[0].additions).toBe(3) + expect(diffs[0].deletions).toBe(0) + }), + ), + { git: true }, +) + +it.instance( + "diffFull with addition and deletion", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/added.txt`, "added content") + yield* rm(`${tmp.path}/a.txt`) + const after = yield* snapshot.track() expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.length).toBe(2) - - const addedFileDiff = diffs.find((d) => d.file === "added.txt") - expect(addedFileDiff).toBeDefined() - expect(addedFileDiff!.patch).toContain("+added content") - expect(addedFileDiff!.additions).toBe(1) - expect(addedFileDiff!.deletions).toBe(0) - - const removedFileDiff = diffs.find((d) => d.file === "a.txt") - expect(removedFileDiff).toBeDefined() - expect(removedFileDiff!.patch).toContain(`-${tmp.extra.aContent}`) - expect(removedFileDiff!.additions).toBe(0) - expect(removedFileDiff!.deletions).toBe(1) - }, - }) -}) - -test("diffFull with multiple additions and deletions", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/multi1.txt`, "line1\nline2\nline3") - await Filesystem.write(`${tmp.path}/multi2.txt`, "single line") - await $`rm ${tmp.path}/a.txt`.quiet() - await $`rm ${tmp.path}/b.txt`.quiet() - - const after = await run(tmp.path, (snapshot) => snapshot.track()) + const added = diffs.find((d) => d.file === "added.txt")! + expect(added.patch).toContain("+added content") + expect(added.additions).toBe(1) + expect(added.deletions).toBe(0) + const removed = diffs.find((d) => d.file === "a.txt")! + expect(removed.patch).toContain(`-${tmp.extra.aContent}`) + expect(removed.additions).toBe(0) + expect(removed.deletions).toBe(1) + }), + ), + { git: true }, +) + +it.instance( + "diffFull with multiple additions and deletions", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/multi1.txt`, "line1\nline2\nline3") + yield* write(`${tmp.path}/multi2.txt`, "single line") + yield* rm(`${tmp.path}/a.txt`) + yield* rm(`${tmp.path}/b.txt`) + const after = yield* snapshot.track() expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.length).toBe(4) - - const multi1Diff = diffs.find((d) => d.file === "multi1.txt") - expect(multi1Diff).toBeDefined() - expect(multi1Diff!.additions).toBe(3) - expect(multi1Diff!.deletions).toBe(0) - - const multi2Diff = diffs.find((d) => d.file === "multi2.txt") - expect(multi2Diff).toBeDefined() - expect(multi2Diff!.additions).toBe(1) - expect(multi2Diff!.deletions).toBe(0) - - const removedADiff = diffs.find((d) => d.file === "a.txt") - expect(removedADiff).toBeDefined() - expect(removedADiff!.additions).toBe(0) - expect(removedADiff!.deletions).toBe(1) - - const removedBDiff = diffs.find((d) => d.file === "b.txt") - expect(removedBDiff).toBeDefined() - expect(removedBDiff!.additions).toBe(0) - expect(removedBDiff!.deletions).toBe(1) - }, - }) -}) - -test("diffFull with no changes", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - const after = await run(tmp.path, (snapshot) => snapshot.track()) - expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) - expect(diffs.length).toBe(0) - }, - }) -}) - -test("diffFull with binary file changes", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/binary.bin`, new Uint8Array([0x00, 0x01, 0x02, 0x03])) - - const after = await run(tmp.path, (snapshot) => snapshot.track()) + expect(diffs.find((d) => d.file === "multi1.txt")!.additions).toBe(3) + expect(diffs.find((d) => d.file === "multi1.txt")!.deletions).toBe(0) + expect(diffs.find((d) => d.file === "multi2.txt")!.additions).toBe(1) + expect(diffs.find((d) => d.file === "multi2.txt")!.deletions).toBe(0) + expect(diffs.find((d) => d.file === "a.txt")!.additions).toBe(0) + expect(diffs.find((d) => d.file === "a.txt")!.deletions).toBe(1) + expect(diffs.find((d) => d.file === "b.txt")!.additions).toBe(0) + expect(diffs.find((d) => d.file === "b.txt")!.deletions).toBe(1) + }), + ), + { git: true }, +) + +it.instance( + "diffFull with no changes", + withTrackedSnapshot(({ snapshot, before }) => + Effect.gen(function* () { + const after = yield* snapshot.track() expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) - expect(diffs.length).toBe(1) - - const binaryDiff = diffs[0] - expect(binaryDiff.file).toBe("binary.bin") - expect(binaryDiff.patch).toBe("") - }, - }) -}) - -test("diffFull with whitespace changes", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await Filesystem.write(`${tmp.path}/whitespace.txt`, "line1\nline2") - const before = await run(tmp.path, (snapshot) => snapshot.track()) - expect(before).toBeTruthy() - - await Filesystem.write(`${tmp.path}/whitespace.txt`, "line1\n\nline2\n") - - const after = await run(tmp.path, (snapshot) => snapshot.track()) + expect((yield* snapshot.diffFull(before, after!)).length).toBe(0) + }), + ), + { git: true }, +) + +it.instance( + "diffFull with binary file changes", + withTrackedSnapshot(({ tmp, snapshot, before }) => + Effect.gen(function* () { + yield* write(`${tmp.path}/binary.bin`, new Uint8Array([0x00, 0x01, 0x02, 0x03])) + const after = yield* snapshot.track() expect(after).toBeTruthy() - - const diffs = await run(tmp.path, (snapshot) => snapshot.diffFull(before!, after!)) + const diffs = yield* snapshot.diffFull(before, after!) expect(diffs.length).toBe(1) - - const whitespaceDiff = diffs[0] - expect(whitespaceDiff.file).toBe("whitespace.txt") - expect(whitespaceDiff.additions).toBeGreaterThan(0) - }, - }) -}) - -test("revert with overlapping files across patches uses first patch hash", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - // Write initial content and snapshot - await Filesystem.write(`${tmp.path}/shared.txt`, "v1") - const snap1 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(snap1).toBeTruthy() - - // Modify and snapshot again - await Filesystem.write(`${tmp.path}/shared.txt`, "v2") - const snap2 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(snap2).toBeTruthy() - - // Modify once more so both patches include shared.txt - await Filesystem.write(`${tmp.path}/shared.txt`, "v3") - - const patch1 = await run(tmp.path, (snapshot) => snapshot.patch(snap1!)) - const patch2 = await run(tmp.path, (snapshot) => snapshot.patch(snap2!)) - - // Both patches should include shared.txt - expect(patch1.files).toContain(fwd(tmp.path, "shared.txt")) - expect(patch2.files).toContain(fwd(tmp.path, "shared.txt")) - - // Revert with patch1 first — should use snap1's hash (restoring "v1") - await run(tmp.path, (snapshot) => snapshot.revert([patch1, patch2])) - - const content = await fs.readFile(`${tmp.path}/shared.txt`, "utf-8") - expect(content).toBe("v1") - }, - }) -}) - -test("revert preserves patch order when the same hash appears again", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await $`mkdir -p ${tmp.path}/foo`.quiet() - await Filesystem.write(`${tmp.path}/foo/bar`, "v1") - await Filesystem.write(`${tmp.path}/a.txt`, "v1") - - const snap1 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(snap1).toBeTruthy() - - await $`rm -rf ${tmp.path}/foo`.quiet() - await Filesystem.write(`${tmp.path}/foo`, "v2") - await Filesystem.write(`${tmp.path}/a.txt`, "v2") - - const snap2 = await run(tmp.path, (snapshot) => snapshot.track()) - expect(snap2).toBeTruthy() - - await $`rm -rf ${tmp.path}/foo`.quiet() - await Filesystem.write(`${tmp.path}/a.txt`, "v3") - - await run(tmp.path, (snapshot) => - snapshot.revert([ - { hash: snap1!, files: [fwd(tmp.path, "a.txt")] }, - { hash: snap2!, files: [fwd(tmp.path, "foo")] }, - { hash: snap1!, files: [fwd(tmp.path, "foo", "bar")] }, - ]), - ) - - expect(await fs.readFile(`${tmp.path}/a.txt`, "utf-8")).toBe("v1") - expect((await fs.stat(`${tmp.path}/foo`)).isDirectory()).toBe(true) - expect(await fs.readFile(`${tmp.path}/foo/bar`, "utf-8")).toBe("v1") - }, - }) -}) - -test("revert handles large mixed batches across chunk boundaries", async () => { - await using tmp = await bootstrap() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const base = Array.from({ length: 140 }, (_, i) => fwd(tmp.path, "batch", `${i}.txt`)) - const fresh = Array.from({ length: 140 }, (_, i) => fwd(tmp.path, "fresh", `${i}.txt`)) - - await $`mkdir -p ${tmp.path}/batch ${tmp.path}/fresh`.quiet() - await Promise.all(base.map((file, i) => Filesystem.write(file, `base-${i}`))) - - const snap = await run(tmp.path, (snapshot) => snapshot.track()) - expect(snap).toBeTruthy() - - await Promise.all(base.map((file, i) => Filesystem.write(file, `next-${i}`))) - await Promise.all(fresh.map((file, i) => Filesystem.write(file, `fresh-${i}`))) - - const patch = await run(tmp.path, (snapshot) => snapshot.patch(snap!)) - expect(patch.files.length).toBe(base.length + fresh.length) - - await run(tmp.path, (snapshot) => snapshot.revert([patch])) - - await Promise.all( - base.map(async (file, i) => { - expect(await fs.readFile(file, "utf-8")).toBe(`base-${i}`) - }), - ) - - await Promise.all( - fresh.map(async (file) => { - expect( - await fs - .access(file) - .then(() => true) - .catch(() => false), - ).toBe(false) - }), - ) - }, - }) -}) + expect(diffs[0].file).toBe("binary.bin") + expect(diffs[0].patch).toBe("") + }), + ), + { git: true }, +) + +it.instance( + "diffFull with whitespace changes", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + yield* write(`${tmp.path}/whitespace.txt`, "line1\nline2") + const before = yield* snapshot.track() + expect(before).toBeTruthy() + yield* write(`${tmp.path}/whitespace.txt`, "line1\n\nline2\n") + const after = yield* snapshot.track() + expect(after).toBeTruthy() + const diffs = yield* snapshot.diffFull(before!, after!) + expect(diffs.length).toBe(1) + expect(diffs[0].file).toBe("whitespace.txt") + expect(diffs[0].additions).toBeGreaterThan(0) + }), + { git: true }, +) + +it.instance( + "revert with overlapping files across patches uses first patch hash", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + yield* write(`${tmp.path}/shared.txt`, "v1") + const snap1 = yield* snapshot.track() + expect(snap1).toBeTruthy() + yield* write(`${tmp.path}/shared.txt`, "v2") + const snap2 = yield* snapshot.track() + expect(snap2).toBeTruthy() + yield* write(`${tmp.path}/shared.txt`, "v3") + const patch1 = yield* snapshot.patch(snap1!) + const patch2 = yield* snapshot.patch(snap2!) + expect(patch1.files).toContain(fwd(tmp.path, "shared.txt")) + expect(patch2.files).toContain(fwd(tmp.path, "shared.txt")) + yield* snapshot.revert([patch1, patch2]) + expect(yield* readText(`${tmp.path}/shared.txt`)).toBe("v1") + }), + { git: true }, +) + +it.instance( + "revert preserves patch order when the same hash appears again", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + yield* mkdirp(`${tmp.path}/foo`) + yield* write(`${tmp.path}/foo/bar`, "v1") + yield* write(`${tmp.path}/a.txt`, "v1") + const snap1 = yield* snapshot.track() + expect(snap1).toBeTruthy() + yield* rm(`${tmp.path}/foo`) + yield* write(`${tmp.path}/foo`, "v2") + yield* write(`${tmp.path}/a.txt`, "v2") + const snap2 = yield* snapshot.track() + expect(snap2).toBeTruthy() + yield* rm(`${tmp.path}/foo`) + yield* write(`${tmp.path}/a.txt`, "v3") + yield* snapshot.revert([ + { hash: snap1!, files: [fwd(tmp.path, "a.txt")] }, + { hash: snap2!, files: [fwd(tmp.path, "foo")] }, + { hash: snap1!, files: [fwd(tmp.path, "foo", "bar")] }, + ]) + expect(yield* readText(`${tmp.path}/a.txt`)).toBe("v1") + expect((yield* Effect.promise(() => fs.stat(`${tmp.path}/foo`))).isDirectory()).toBe(true) + expect(yield* readText(`${tmp.path}/foo/bar`)).toBe("v1") + }), + { git: true }, +) + +it.instance( + "revert handles large mixed batches across chunk boundaries", + Effect.gen(function* () { + const tmp = yield* bootstrap() + const snapshot = yield* Snapshot.Service + const base = Array.from({ length: 140 }, (_, i) => fwd(tmp.path, "batch", `${i}.txt`)) + const fresh = Array.from({ length: 140 }, (_, i) => fwd(tmp.path, "fresh", `${i}.txt`)) + yield* mkdirp(`${tmp.path}/batch`) + yield* mkdirp(`${tmp.path}/fresh`) + yield* Effect.all( + base.map((file, i) => write(file, `base-${i}`)), + { concurrency: "unbounded" }, + ) + const snap = yield* snapshot.track() + expect(snap).toBeTruthy() + yield* Effect.all( + [...base.map((file, i) => write(file, `next-${i}`)), ...fresh.map((file, i) => write(file, `fresh-${i}`))], + { concurrency: "unbounded" }, + ) + const patch = yield* snapshot.patch(snap!) + expect(patch.files.length).toBe(base.length + fresh.length) + yield* snapshot.revert([patch]) + for (let i = 0; i < base.length; i++) expect(yield* readText(base[i])).toBe(`base-${i}`) + for (const file of fresh) expect(yield* exists(file)).toBe(false) + }), + { git: true }, +) From c4003579bbe6d943562814686a0cdd5a1357f784 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 20:59:34 -0400 Subject: [PATCH 063/378] test(project): migrate VCS tests to Effect runner (#26965) --- packages/opencode/test/project/vcs.test.ts | 583 ++++++++++----------- 1 file changed, 280 insertions(+), 303 deletions(-) diff --git a/packages/opencode/test/project/vcs.test.ts b/packages/opencode/test/project/vcs.test.ts index 82eacfb6d..75d1feadd 100644 --- a/packages/opencode/test/project/vcs.test.ts +++ b/packages/opencode/test/project/vcs.test.ts @@ -1,161 +1,139 @@ -import { $ } from "bun" -import { afterEach, describe, expect, test } from "bun:test" +import { afterEach, describe, expect } from "bun:test" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { parsePatch } from "diff" -import { Effect } from "effect" +import { Deferred, Effect, Layer, Stream } from "effect" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import fs from "fs/promises" import path from "path" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" -import { AppRuntime } from "../../src/effect/app-runtime" +import { disposeAllInstances, provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" +import { Bus } from "../../src/bus" import { FileWatcher } from "../../src/file/watcher" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" -import { GlobalBus } from "../../src/bus/global" +import { Git } from "../../src/git" import { Vcs } from "@/project/vcs" - -// Skip in CI — native @parcel/watcher binding needed -const describeVcs = FileWatcher.hasNativeBinding() && !process.env.CI ? describe : describe.skip +import { testEffect } from "../lib/effect" // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- -async function withVcs(directory: string, body: () => Promise) { - return WithInstance.provide({ - directory, - fn: async () => { - await AppRuntime.runPromise( - Effect.gen(function* () { - const watcher = yield* FileWatcher.Service - const vcs = yield* Vcs.Service - yield* watcher.init() - yield* vcs.init() - }), - ) - await Bun.sleep(500) - await body() - }, - }) -} - -function withVcsOnly(directory: string, body: () => Promise) { - return WithInstance.provide({ - directory, - fn: async () => { - await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - yield* vcs.init() - }), - ) - await body() - }, - }) -} - -type BranchEvent = { directory?: string; payload: { type: string; properties: { branch?: string } } } const weird = process.platform === "win32" ? "space file.txt" : "tab\tfile.txt" -/** Wait for a Vcs.Event.BranchUpdated event on GlobalBus, with retry polling as fallback */ -function nextBranchUpdate(directory: string, timeout = 10_000) { - return new Promise((resolve, reject) => { - let settled = false - - const timer = setTimeout(() => { - if (settled) return - settled = true - GlobalBus.off("event", on) - reject(new Error("timed out waiting for BranchUpdated event")) - }, timeout) - - function on(evt: BranchEvent) { - if (evt.directory !== directory) return - if (evt.payload.type !== Vcs.Event.BranchUpdated.type) return - if (settled) return - settled = true - clearTimeout(timer) - GlobalBus.off("event", on) - resolve(evt.payload.properties.branch) - } - - GlobalBus.on("event", on) - }) -} +const layer = Layer.mergeAll( + Vcs.layer.pipe(Layer.provideMerge(Git.defaultLayer), Layer.provideMerge(Bus.layer)), + CrossSpawnSpawner.defaultLayer, + AppFileSystem.defaultLayer, +) +const it = testEffect(layer) + +const git = Effect.fn("VcsTest.git")(function* (cwd: string, args: string[]) { + const result = yield* Git.Service.use((git) => git.run(args, { cwd })) + if (result.exitCode !== 0) throw new Error(`git ${args.join(" ")} failed: ${result.stderr.toString("utf8")}`) +}) + +const write = Effect.fn("VcsTest.write")(function* (file: string, content: string) { + yield* AppFileSystem.Service.use((fs) => fs.writeWithDirs(file, content)) +}) + +const remove = Effect.fn("VcsTest.remove")(function* (file: string) { + yield* AppFileSystem.Service.use((fs) => fs.remove(file)) +}) + +const symlink = (target: string, file: string) => Effect.promise(() => fs.symlink(target, file)) + +const init = Effect.fn("VcsTest.init")(function* () { + const vcs = yield* Vcs.Service + yield* vcs.init() + return vcs +}) + +const nextBranchUpdate = Effect.fn("VcsTest.nextBranchUpdate")(function* () { + const bus = yield* Bus.Service + const updated = yield* Deferred.make() + + yield* Stream.runForEach(bus.subscribe(Vcs.Event.BranchUpdated), (evt) => + Deferred.succeed(updated, evt.properties.branch), + ).pipe(Effect.forkScoped) + + return updated +}) // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- -describeVcs("Vcs", () => { +describe("Vcs", () => { afterEach(async () => { await disposeAllInstances() }) - test("branch() returns current branch name", async () => { - await using tmp = await tmpdir({ git: true }) + it.instance( + "branch() returns current branch name", + () => + Effect.gen(function* () { + const vcs = yield* init() + const branch = yield* vcs.branch() - await withVcs(tmp.path, async () => { - const branch = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.branch() - }), - ) - expect(branch).toBeDefined() - expect(typeof branch).toBe("string") - }) - }) + expect(branch).toBeDefined() + expect(typeof branch).toBe("string") + }), + { git: true }, + ) - test("branch() returns undefined for non-git directories", async () => { - await using tmp = await tmpdir() + it.instance("branch() returns undefined for non-git directories", () => + Effect.gen(function* () { + const vcs = yield* init() + const branch = yield* vcs.branch() - await withVcs(tmp.path, async () => { - const branch = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.branch() - }), - ) expect(branch).toBeUndefined() - }) - }) - - test("publishes BranchUpdated when .git/HEAD changes", async () => { - await using tmp = await tmpdir({ git: true }) - const branch = `test-${Math.random().toString(36).slice(2)}` - await $`git branch ${branch}`.cwd(tmp.path).quiet() - - await withVcs(tmp.path, async () => { - const pending = nextBranchUpdate(tmp.path) - - const head = path.join(tmp.path, ".git", "HEAD") - await fs.writeFile(head, `ref: refs/heads/${branch}\n`) - - const updated = await pending - expect(updated).toBe(branch) - }) - }) - - test("branch() reflects the new branch after HEAD change", async () => { - await using tmp = await tmpdir({ git: true }) - const branch = `test-${Math.random().toString(36).slice(2)}` - await $`git branch ${branch}`.cwd(tmp.path).quiet() - - await withVcs(tmp.path, async () => { - const pending = nextBranchUpdate(tmp.path) - - const head = path.join(tmp.path, ".git", "HEAD") - await fs.writeFile(head, `ref: refs/heads/${branch}\n`) - - await pending - const current = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.branch() - }), - ) - expect(current).toBe(branch) - }) - }) + }), + ) + + it.instance( + "publishes BranchUpdated when .git/HEAD changes", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const branch = `test-${Math.random().toString(36).slice(2)}` + yield* git(test.directory, ["branch", branch]) + + const vcs = yield* init() + yield* vcs.branch() + const pending = yield* nextBranchUpdate() + const bus = yield* Bus.Service + + const head = path.join(test.directory, ".git", "HEAD") + yield* write(head, `ref: refs/heads/${branch}\n`) + yield* bus.publish(FileWatcher.Event.Updated, { file: head, event: "change" }) + + const updated = yield* Deferred.await(pending).pipe(Effect.timeout("2 seconds")) + expect(updated).toBe(branch) + }), + { git: true }, + ) + + it.instance( + "branch() reflects the new branch after HEAD change", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const branch = `test-${Math.random().toString(36).slice(2)}` + yield* git(test.directory, ["branch", branch]) + + const vcs = yield* init() + yield* vcs.branch() + const pending = yield* nextBranchUpdate() + const bus = yield* Bus.Service + + const head = path.join(test.directory, ".git", "HEAD") + yield* write(head, `ref: refs/heads/${branch}\n`) + yield* bus.publish(FileWatcher.Event.Updated, { file: head, event: "change" }) + yield* Deferred.await(pending).pipe(Effect.timeout("2 seconds")) + + const current = yield* vcs.branch() + expect(current).toBe(branch) + }), + { git: true }, + ) }) describe("Vcs diff", () => { @@ -163,177 +141,176 @@ describe("Vcs diff", () => { await disposeAllInstances() }) - test("defaultBranch() falls back to main", async () => { - await using tmp = await tmpdir({ git: true }) - await $`git branch -M main`.cwd(tmp.path).quiet() - - await withVcsOnly(tmp.path, async () => { - const branch = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.defaultBranch() - }), - ) - expect(branch).toBe("main") - }) - }) - - test("defaultBranch() uses init.defaultBranch when available", async () => { - await using tmp = await tmpdir({ git: true }) - await $`git branch -M trunk`.cwd(tmp.path).quiet() - await $`git config init.defaultBranch trunk`.cwd(tmp.path).quiet() - - await withVcsOnly(tmp.path, async () => { - const branch = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.defaultBranch() - }), - ) - expect(branch).toBe("trunk") - }) - }) + it.instance( + "defaultBranch() falls back to main", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* git(test.directory, ["branch", "-M", "main"]) + + const vcs = yield* init() + const branch = yield* vcs.defaultBranch() + + expect(branch).toBe("main") + }), + { git: true }, + ) + + it.instance( + "defaultBranch() uses init.defaultBranch when available", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* git(test.directory, ["branch", "-M", "trunk"]) + yield* git(test.directory, ["config", "init.defaultBranch", "trunk"]) + + const vcs = yield* init() + const branch = yield* vcs.defaultBranch() + + expect(branch).toBe("trunk") + }), + { git: true }, + ) + + it.live("detects current branch from the active worktree", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped({ git: true }) + const wt = yield* tmpdirScoped() + yield* git(tmp, ["branch", "-M", "main"]) + const dir = path.join(wt, "feature") + yield* git(tmp, ["worktree", "add", "-b", "feature/test", dir, "HEAD"]) + + const [branch, base] = yield* Effect.gen(function* () { + const vcs = yield* init() + return yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 }) + }).pipe(provideInstance(dir)) - test("detects current branch from the active worktree", async () => { - await using tmp = await tmpdir({ git: true }) - await using wt = await tmpdir() - await $`git branch -M main`.cwd(tmp.path).quiet() - const dir = path.join(wt.path, "feature") - await $`git worktree add -b feature/test ${dir} HEAD`.cwd(tmp.path).quiet() - - await withVcsOnly(dir, async () => { - const [branch, base] = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 }) - }), - ) + expect(branch).toBeDefined() expect(branch).toBe("feature/test") expect(base).toBe("main") - }) - }) - - test("diff('git') returns uncommitted changes", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "file.txt"), "original\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet() - await fs.writeFile(path.join(tmp.path, "file.txt"), "changed\n", "utf-8") - - await withVcsOnly(tmp.path, async () => { - const diff = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.diff("git") - }), - ) - expect(diff).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - file: "file.txt", - status: "modified", - }), - ]), - ) - expect(diff.find((item) => item.file === "file.txt")?.patch).toContain("diff --git") - }) - }) - - test("diff('git') handles special filenames", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, weird), "hello\n", "utf-8") - - await withVcsOnly(tmp.path, async () => { - const diff = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.diff("git") - }), - ) - expect(diff).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - file: weird, - status: "added", - }), - ]), - ) - }) - }) - - test("diff('git') keeps batched patches aligned for type changes", async () => { - if (process.platform === "win32") return - - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "a.txt"), "old\n", "utf-8") - await fs.writeFile(path.join(tmp.path, "b.txt"), "old\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit --no-gpg-sign -m "add files"`.cwd(tmp.path).quiet() - await fs.unlink(path.join(tmp.path, "a.txt")) - await fs.symlink("target", path.join(tmp.path, "a.txt")) - await fs.writeFile(path.join(tmp.path, "b.txt"), "new\n", "utf-8") - - await withVcsOnly(tmp.path, async () => { - const diff = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.diff("git") - }), - ) - const a = diff.find((item) => item.file === "a.txt") - const b = diff.find((item) => item.file === "b.txt") - - expect(a?.patch).toContain("deleted file mode") - expect(a?.patch).toContain("new file mode") - expect(b?.patch).toContain("+new") - }) - }) - - test("diff('git') keeps carriage returns inside patch hunks", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "file.txt"), "keep\nsame\rdiff --git inside\ndelete\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet() - await fs.writeFile(path.join(tmp.path, "file.txt"), "keep\nadd\nsame\rdiff --git inside\n", "utf-8") - - await withVcsOnly(tmp.path, async () => { - const diff = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.diff("git") - }), - ) - const file = diff.find((item) => item.file === "file.txt") - - expect(file?.patch).toContain(" same\rdiff --git inside") - expect(file?.patch).toContain("-delete") - expect(() => parsePatch(file?.patch ?? "")).not.toThrow() - }) - }, 20_000) - - test("diff('branch') returns changes against default branch", async () => { - await using tmp = await tmpdir({ git: true }) - await $`git branch -M main`.cwd(tmp.path).quiet() - await $`git checkout -b feature/test`.cwd(tmp.path).quiet() - await fs.writeFile(path.join(tmp.path, "branch.txt"), "hello\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit --no-gpg-sign -m "branch file"`.cwd(tmp.path).quiet() - - await withVcsOnly(tmp.path, async () => { - const diff = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.diff("branch") - }), - ) - expect(diff).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - file: "branch.txt", - status: "added", - }), - ]), - ) - }) - }) + }), + ) + + it.instance( + "diff('git') returns uncommitted changes", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* write(path.join(test.directory, "file.txt"), "original\n") + yield* git(test.directory, ["add", "."]) + yield* git(test.directory, ["commit", "--no-gpg-sign", "-m", "add file"]) + yield* write(path.join(test.directory, "file.txt"), "changed\n") + + const vcs = yield* init() + const diff = yield* vcs.diff("git") + + expect(diff).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + file: "file.txt", + status: "modified", + }), + ]), + ) + expect(diff.find((item) => item.file === "file.txt")?.patch).toContain("diff --git") + }), + { git: true }, + ) + + it.instance( + "diff('git') handles special filenames", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* write(path.join(test.directory, weird), "hello\n") + + const vcs = yield* init() + const diff = yield* vcs.diff("git") + + expect(diff).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + file: weird, + status: "added", + }), + ]), + ) + }), + { git: true }, + ) + + it.instance( + "diff('git') keeps batched patches aligned for type changes", + () => + Effect.gen(function* () { + if (process.platform === "win32") return + + const test = yield* TestInstance + yield* write(path.join(test.directory, "a.txt"), "old\n") + yield* write(path.join(test.directory, "b.txt"), "old\n") + yield* git(test.directory, ["add", "."]) + yield* git(test.directory, ["commit", "--no-gpg-sign", "-m", "add files"]) + yield* remove(path.join(test.directory, "a.txt")) + yield* symlink("target", path.join(test.directory, "a.txt")) + yield* write(path.join(test.directory, "b.txt"), "new\n") + + const vcs = yield* init() + const diff = yield* vcs.diff("git") + const a = diff.find((item) => item.file === "a.txt") + const b = diff.find((item) => item.file === "b.txt") + + expect(a?.patch).toContain("deleted file mode") + expect(a?.patch).toContain("new file mode") + expect(b?.patch).toContain("+new") + }), + { git: true }, + ) + + it.instance( + "diff('git') keeps carriage returns inside patch hunks", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* write(path.join(test.directory, "file.txt"), "keep\nsame\rdiff --git inside\ndelete\n") + yield* git(test.directory, ["add", "."]) + yield* git(test.directory, ["commit", "--no-gpg-sign", "-m", "add file"]) + yield* write(path.join(test.directory, "file.txt"), "keep\nadd\nsame\rdiff --git inside\n") + + const vcs = yield* init() + const diff = yield* vcs.diff("git") + const file = diff.find((item) => item.file === "file.txt") + + expect(file?.patch).toContain(" same\rdiff --git inside") + expect(file?.patch).toContain("-delete") + expect(() => parsePatch(file?.patch ?? "")).not.toThrow() + }), + { git: true }, + 20_000, + ) + + it.instance( + "diff('branch') returns changes against default branch", + () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* git(test.directory, ["branch", "-M", "main"]) + yield* git(test.directory, ["checkout", "-b", "feature/test"]) + yield* write(path.join(test.directory, "branch.txt"), "hello\n") + yield* git(test.directory, ["add", "."]) + yield* git(test.directory, ["commit", "--no-gpg-sign", "-m", "branch file"]) + + const vcs = yield* init() + const diff = yield* vcs.diff("branch") + + expect(diff).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + file: "branch.txt", + status: "added", + }), + ]), + ) + }), + { git: true }, + ) }) From abb1ee627858be10a88be1b63500e0b770fb9e55 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 20:59:51 -0400 Subject: [PATCH 064/378] docs(test): add Effect migration orchestration notes (#26963) --- packages/opencode/test/EFFECT_TEST_MIGRATION.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/EFFECT_TEST_MIGRATION.md b/packages/opencode/test/EFFECT_TEST_MIGRATION.md index 60cd33264..2c160b993 100644 --- a/packages/opencode/test/EFFECT_TEST_MIGRATION.md +++ b/packages/opencode/test/EFFECT_TEST_MIGRATION.md @@ -200,11 +200,20 @@ Use this as a migration queue. Each checkbox should be safe for one agent or one Parallelization notes: -- The first four items are mostly independent and good for parallel agents. +- The first four items are mostly independent and good for separate worktrees. - `provider.test.ts`, `tool/edit.test.ts`, and `config.test.ts` should be split by cluster so agents do not edit the same file concurrently. - Any new fake boundary layer under `test/fake/*` should be small and independently useful. Do not add a fake just for one assertion unless it removes a real external dependency. - Do not combine assertion-helper design with file migrations. First collect repeated shapes, then add helpers in a separate pass. +Orchestration rules: + +- Prefer supervised foreground agents for implementation. Background agents are acceptable for research-only surveys, but code migrations need a returned diff, focused test output, and local commit before moving on. +- Create one worktree per claim and verify the branch/worktree path before edits. A status check should include `git status --short --branch` from the claimed worktree. +- After an agent reports completion, the coordinator must independently inspect `git status`, run the focused test, run `bun typecheck`, and review the diff before pushing. +- If an agent edits the wrong worktree, move the patch deliberately with `git diff` / `git apply`, then clean the accidental worktree before opening a PR. +- Keep dependency setup boring. Prefer reusing existing installed dependencies via worktrees or symlinks over running a fresh `bun install` in a temporary path unless the native build path is known to work. +- Do not delete worktrees with unpushed commits or uncommitted changes. Once a migration PR branch is pushed and clean, the local worktree can be removed while leaving the branch on the fork. + ## Effectified Test Rough Edges Track patterns that are technically Effect-native but still too noisy. These should become a second cleanup pass after the Promise-land migration is underway. From e5aa5161f2317f466cdb5eb2fe97b16120ddded5 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 21:14:55 -0400 Subject: [PATCH 065/378] Remove effect-zod bridge (#26956) --- packages/core/src/effect-zod.ts | 370 --------- packages/core/src/schema.ts | 2 - packages/opencode/specs/effect/migration.md | 12 +- packages/opencode/specs/effect/schema.md | 84 +- .../specs/openapi-translation-cleanup.md | 2 +- packages/opencode/src/command/index.ts | 5 +- packages/opencode/src/config/model-id.ts | 10 +- packages/opencode/src/lsp/lsp.ts | 6 +- .../instance/httpapi/handlers/experimental.ts | 4 +- packages/opencode/src/session/message-v2.ts | 3 +- packages/opencode/src/session/prompt.ts | 4 +- packages/opencode/src/tool/json-schema.ts | 164 ++++ packages/opencode/src/tool/registry.ts | 72 +- packages/opencode/src/tool/tool.ts | 2 + packages/opencode/src/tool/webfetch.ts | 5 +- .../opencode/src/util/named-schema-error.ts | 16 +- packages/opencode/test/session/retry.test.ts | 24 +- .../__snapshots__/parameters.test.ts.snap | 24 +- .../opencode/test/tool/parameters.test.ts | 38 +- packages/opencode/test/tool/registry.test.ts | 90 ++- .../opencode/test/util/effect-zod.test.ts | 754 ------------------ 21 files changed, 425 insertions(+), 1266 deletions(-) delete mode 100644 packages/core/src/effect-zod.ts create mode 100644 packages/opencode/src/tool/json-schema.ts delete mode 100644 packages/opencode/test/util/effect-zod.test.ts diff --git a/packages/core/src/effect-zod.ts b/packages/core/src/effect-zod.ts deleted file mode 100644 index 42d89ec7d..000000000 --- a/packages/core/src/effect-zod.ts +++ /dev/null @@ -1,370 +0,0 @@ -import { Effect, Option, Schema, SchemaAST } from "effect" -import z from "zod" - -/** - * Annotation key for providing a hand-crafted Zod schema that the walker - * should use instead of re-deriving from the AST. Attach it via - * `Schema.String.annotate({ [ZodOverride]: z.string().startsWith("per") })`. - */ -export const ZodOverride: unique symbol = Symbol.for("effect-zod/override") - -// AST nodes are immutable and frequently shared across schemas (e.g. a single -// Schema.Class embedded in multiple parents). Memoizing by node identity -// avoids rebuilding equivalent Zod subtrees and keeps derived children stable -// by reference across callers. -const walkCache = new WeakMap() - -// Shared empty ParseOptions for the rare callers that need one — avoids -// allocating a fresh object per parse inside refinements and transforms. -const EMPTY_PARSE_OPTIONS = {} as SchemaAST.ParseOptions - -export function zod(schema: S): z.ZodType> { - return walk(schema.ast) as z.ZodType> -} - -/** - * Derive a Zod value from an Effect Schema (or a Schema-backed export with a - * `.zod` static) and narrow the result to `z.ZodObject` so `.shape`, - * `.omit`, `.extend`, and friends are accessible. - * - * The `zod()` walker returns `z.ZodType` because not every AST node decodes - * to an object; this helper keeps the "I started from a `Schema.Struct`" cast - * in one place instead of sprinkling `as unknown as z.ZodObject` across - * call sites. - * - * The return is intentionally loose — carrying Schema field types through the - * mapped `.omit()` / `.extend()` surface triggers brand-intersection - * explosions for branded primitives (`string & Brand<"SessionID">` extends - * `object` via the brand and gets walked into the prototype by `DeepPartial`, - * mapped-schema helpers, and zod's inference through `z.ZodType` - * wrappers also can't reconstruct `T` cleanly. Consumers that care about the - * post-`.omit()` shape should cast `c.req.valid(...)` to the expected type. - */ -export function zodObject(schema: S): z.ZodObject { - const derived: z.ZodTypeAny = "zod" in schema && isZodType(schema.zod) ? schema.zod : walk(schema.ast) - return derived as unknown as z.ZodObject -} - -function isZodType(value: unknown): value is z.ZodTypeAny { - return typeof value === "object" && value !== null && "_zod" in value -} - -/** - * Emit a JSON Schema for a tool/route parameter schema — derives the zod form - * via the walker so Effect Schema inputs flow through the same zod-openapi - * pipeline the LLM/SDK layer already depends on. `io: "input"` mirrors what - * `session/prompt.ts` has always passed to `ai`'s `jsonSchema()` helper. - */ -export function toJsonSchema(schema: S) { - return z.toJSONSchema(zod(schema), { io: "input" }) -} - -function walk(ast: SchemaAST.AST): z.ZodTypeAny { - const cached = walkCache.get(ast) - if (cached) return cached - const result = walkUncached(ast) - walkCache.set(ast, result) - return result -} - -function walkUncached(ast: SchemaAST.AST): z.ZodTypeAny { - const override = (ast.annotations as any)?.[ZodOverride] as z.ZodTypeAny | undefined - // `description` annotations layer on top of an override so callers can - // reuse a shared override schema (e.g. `SessionID`) and still add a - // per-field description on the outer wrapper. - const base = override ?? bodyWithChecks(ast) - const desc = SchemaAST.resolveDescription(ast) - const ref = SchemaAST.resolveIdentifier(ast) - const described = desc ? base.describe(desc) : base - return ref ? described.meta({ ref }) : described -} - -function bodyWithChecks(ast: SchemaAST.AST): z.ZodTypeAny { - // Schema.Class wraps its fields in a Declaration AST plus an encoding that - // constructs the class instance. For the Zod derivation we want the plain - // field shape (the decoded/consumer view), not the class instance — so - // Declarations fall through to body(), not encoded(). User-level - // Schema.decodeTo / Schema.transform attach encoding to non-Declaration - // nodes, where we do apply the transform. - // - // Schema.withDecodingDefault also attaches encoding, but we want `.default(v)` - // on the inner Zod rather than a transform wrapper — so optional ASTs whose - // encoding resolves a default from Option.none() route through body()/opt(). - const hasEncoding = ast.encoding?.length && (ast._tag !== "Declaration" || ast.typeParameters.length === 0) - const hasTransform = hasEncoding && !(SchemaAST.isOptional(ast) && extractDefault(ast) !== undefined) - const base = hasTransform ? encoded(ast) : body(ast) - return ast.checks?.length ? applyChecks(base, ast.checks, ast) : base -} - -// Walk the encoded side and apply each link's decode to produce the decoded -// shape. A node `Target` produced by `from.decodeTo(Target)` carries -// `Target.encoding = [Link(from, transformation)]`. Chained decodeTo calls -// nest the encoding via `Link.to` so walking it recursively threads all -// prior transforms — typical encoding.length is 1. -function encoded(ast: SchemaAST.AST): z.ZodTypeAny { - const encoding = ast.encoding! - return encoding.reduce( - (acc, link) => acc.transform((v) => decode(link.transformation, v)), - walk(encoding[0].to), - ) -} - -// Transformations built via pure `SchemaGetter.transform(fn)` (the common -// decodeTo case) resolve synchronously, so running with no services is safe. -// Effectful / middleware-based transforms will surface as Effect defects. -function decode(transformation: SchemaAST.Link["transformation"], value: unknown): unknown { - const exit = Effect.runSyncExit( - (transformation.decode as any).run(Option.some(value), EMPTY_PARSE_OPTIONS) as Effect.Effect< - Option.Option - >, - ) - if (exit._tag === "Failure") throw new Error(`effect-zod: transform failed: ${String(exit.cause)}`) - return Option.getOrElse(exit.value, () => value) -} - -// Flatten FilterGroups and any nested variants into a linear list of Filters. -// Well-known filters (Schema.isInt, isGreaterThan, isPattern, …) are -// translated into native Zod methods so their JSON Schema output includes -// the corresponding constraint (type: integer, exclusiveMinimum, pattern, …). -// Anything else falls back to a single .superRefine layer — runtime-only, -// emits no JSON Schema constraint. -function applyChecks(out: z.ZodTypeAny, checks: SchemaAST.Checks, ast: SchemaAST.AST): z.ZodTypeAny { - const filters: SchemaAST.Filter[] = [] - const collect = (c: SchemaAST.Check) => { - if (c._tag === "FilterGroup") c.checks.forEach(collect) - else filters.push(c) - } - checks.forEach(collect) - - const unhandled: SchemaAST.Filter[] = [] - const translated = filters.reduce((acc, filter) => { - const next = translateFilter(acc, filter) - if (next) return next - unhandled.push(filter) - return acc - }, out) - - if (unhandled.length === 0) return translated - - return translated.superRefine((value, ctx) => { - for (const filter of unhandled) { - const issue = filter.run(value, ast, EMPTY_PARSE_OPTIONS) - if (!issue) continue - const message = issueMessage(issue) ?? (filter.annotations as any)?.message ?? "Validation failed" - ctx.addIssue({ code: "custom", message }) - } - }) -} - -// Translate a well-known Effect Schema filter into a native Zod method call on -// `out`. Dispatch is keyed on `filter.annotations.meta._tag`, which every -// built-in check factory (isInt, isGreaterThan, isPattern, …) attaches at -// construction time. Returns `undefined` for unrecognised filters so the -// caller can fall back to the generic .superRefine path. -function translateFilter(out: z.ZodTypeAny, filter: SchemaAST.Filter): z.ZodTypeAny | undefined { - const meta = (filter.annotations as { meta?: Record } | undefined)?.meta - if (!meta || typeof meta._tag !== "string") return undefined - switch (meta._tag) { - case "isInt": - return call(out, "int") - case "isFinite": - return call(out, "finite") - case "isGreaterThan": - return call(out, "gt", meta.exclusiveMinimum) - case "isGreaterThanOrEqualTo": - return call(out, "gte", meta.minimum) - case "isLessThan": - return call(out, "lt", meta.exclusiveMaximum) - case "isLessThanOrEqualTo": - return call(out, "lte", meta.maximum) - case "isBetween": { - const lo = meta.exclusiveMinimum ? call(out, "gt", meta.minimum) : call(out, "gte", meta.minimum) - if (!lo) return undefined - return meta.exclusiveMaximum ? call(lo, "lt", meta.maximum) : call(lo, "lte", meta.maximum) - } - case "isMultipleOf": - return call(out, "multipleOf", meta.divisor) - case "isMinLength": - return call(out, "min", meta.minLength) - case "isMaxLength": - return call(out, "max", meta.maxLength) - case "isLengthBetween": { - const lo = call(out, "min", meta.minimum) - if (!lo) return undefined - return call(lo, "max", meta.maximum) - } - case "isPattern": - return call(out, "regex", meta.regExp) - case "isStartsWith": - return call(out, "startsWith", meta.startsWith) - case "isEndsWith": - return call(out, "endsWith", meta.endsWith) - case "isIncludes": - return call(out, "includes", meta.includes) - case "isUUID": - return call(out, "uuid") - case "isULID": - return call(out, "ulid") - case "isBase64": - return call(out, "base64") - case "isBase64Url": - return call(out, "base64url") - } - return undefined -} - -// Invoke a named Zod method on `target` if it exists, otherwise return -// undefined so the caller can fall back. Using this helper instead of a -// typed cast keeps `translateFilter` free of per-case narrowing noise. -function call(target: z.ZodTypeAny, method: string, ...args: unknown[]): z.ZodTypeAny | undefined { - const fn = (target as unknown as Record z.ZodTypeAny) | undefined>)[method] - return typeof fn === "function" ? fn.apply(target, args) : undefined -} - -function issueMessage(issue: any): string | undefined { - if (typeof issue?.annotations?.message === "string") return issue.annotations.message - if (typeof issue?.message === "string") return issue.message - return undefined -} - -function body(ast: SchemaAST.AST): z.ZodTypeAny { - if (SchemaAST.isOptional(ast)) return opt(ast) - - switch (ast._tag) { - case "String": - return z.string() - case "Number": - return z.number() - case "Boolean": - return z.boolean() - case "Null": - return z.null() - case "Undefined": - return z.undefined() - case "Any": - case "Unknown": - return z.unknown() - case "Never": - return z.never() - case "Literal": - return z.literal(ast.literal) - case "Union": - return union(ast) - case "Objects": - return object(ast) - case "Arrays": - return array(ast) - case "Declaration": - return decl(ast) - default: - return fail(ast) - } -} - -function opt(ast: SchemaAST.AST): z.ZodTypeAny { - if (ast._tag !== "Union") return fail(ast) - const items = ast.types.filter((item) => item._tag !== "Undefined") - const inner = - items.length === 1 - ? walk(items[0]) - : items.length > 1 - ? z.union(items.map(walk) as [z.ZodTypeAny, z.ZodTypeAny, ...Array]) - : z.undefined() - // Schema.withDecodingDefault attaches an encoding `Link` whose transformation - // decode Getter resolves `Option.none()` to `Option.some(default)`. Invoke - // it to extract the default and emit `.default(...)` instead of `.optional()`. - const fallback = extractDefault(ast) - if (fallback !== undefined) return inner.default(fallback.value) - return inner.optional() -} - -type DecodeLink = { - readonly transformation: { - readonly decode: { - readonly run: ( - input: Option.Option, - options: SchemaAST.ParseOptions, - ) => Effect.Effect, unknown> - } - } -} - -function extractDefault(ast: SchemaAST.AST): { value: unknown } | undefined { - const encoding = (ast as { encoding?: ReadonlyArray }).encoding - if (!encoding?.length) return undefined - // Walk the chain of encoding Links in order; the first Getter that produces - // a value from Option.none wins. withDecodingDefault always puts its - // defaulting Link adjacent to the optional Union. - for (const link of encoding) { - const probe = Effect.runSyncExit(link.transformation.decode.run(Option.none(), {})) - if (probe._tag !== "Success") continue - if (Option.isSome(probe.value)) return { value: probe.value.value } - } - return undefined -} - -function union(ast: SchemaAST.Union): z.ZodTypeAny { - // When every member is a string literal, emit z.enum() so that - // JSON Schema produces { "enum": [...] } instead of { "anyOf": [{ "const": ... }] }. - if (ast.types.length >= 2 && ast.types.every((t) => t._tag === "Literal" && typeof t.literal === "string")) { - return z.enum(ast.types.map((t) => (t as SchemaAST.Literal).literal as string) as [string, ...string[]]) - } - - const items = ast.types.map(walk) - if (items.length === 1) return items[0] - if (items.length < 2) return fail(ast) - - const discriminator = ast.annotations?.discriminator - if (typeof discriminator === "string") { - return z.discriminatedUnion(discriminator, items as [z.ZodObject, z.ZodObject, ...z.ZodObject[]]) - } - - return z.union(items as [z.ZodTypeAny, z.ZodTypeAny, ...Array]) -} - -function object(ast: SchemaAST.Objects): z.ZodTypeAny { - // Pure record: { [k: string]: V } - if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 1) { - const sig = ast.indexSignatures[0] - if (sig.parameter._tag !== "String") return fail(ast) - return z.record(z.string(), walk(sig.type)) - } - - // Pure object with known fields and no index signatures. - if (ast.indexSignatures.length === 0) { - return z.object(Object.fromEntries(ast.propertySignatures.map((sig) => [String(sig.name), walk(sig.type)]))) - } - - // Struct with a catchall (StructWithRest): known fields + index signature. - // Only supports a single string-keyed index signature; multi-signature or - // symbol/number keys fall through to fail. - if (ast.indexSignatures.length !== 1) return fail(ast) - const sig = ast.indexSignatures[0] - if (sig.parameter._tag !== "String") return fail(ast) - return z - .object(Object.fromEntries(ast.propertySignatures.map((p) => [String(p.name), walk(p.type)]))) - .catchall(walk(sig.type)) -} - -function array(ast: SchemaAST.Arrays): z.ZodTypeAny { - // Pure variadic arrays: { elements: [], rest: [item] } - if (ast.elements.length === 0) { - if (ast.rest.length !== 1) return fail(ast) - return z.array(walk(ast.rest[0])) - } - // Fixed-length tuples: { elements: [a, b, ...], rest: [] } - // Tuples with a variadic tail (...rest) are not yet supported. - if (ast.rest.length > 0) return fail(ast) - const items = ast.elements.map(walk) - return z.tuple(items as [z.ZodTypeAny, ...Array]) -} - -function decl(ast: SchemaAST.Declaration): z.ZodTypeAny { - if (ast.typeParameters.length !== 1) return fail(ast) - return walk(ast.typeParameters[0]) -} - -function fail(ast: SchemaAST.AST): never { - const ref = SchemaAST.resolveIdentifier(ast) - throw new Error(`unsupported effect schema: ${ref ?? ast._tag}`) -} diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index 2a6c02349..5b4042c73 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -1,5 +1,4 @@ import { Option, Schema, SchemaGetter } from "effect" -import { zod, ZodOverride } from "./effect-zod" /** * Integer greater than zero. @@ -21,7 +20,6 @@ export const optionalOmitUndefined = (schema: S) => decode: SchemaGetter.passthrough({ strict: false }), encode: SchemaGetter.transformOptional(Option.filter((value) => value !== undefined)), }), - Schema.annotate({ [ZodOverride]: zod(schema).optional() }), ) /** diff --git a/packages/opencode/specs/effect/migration.md b/packages/opencode/specs/effect/migration.md index 01af9da6c..13838e833 100644 --- a/packages/opencode/specs/effect/migration.md +++ b/packages/opencode/specs/effect/migration.md @@ -57,17 +57,9 @@ Rules: - Avoid service-local `makeRuntime(...)` facades unless a file is still intentionally in the older migration phase - No `Layer.fresh` for normal per-directory isolation; use `InstanceState` -## Schema → Zod interop +## Schema boundaries -When a service uses Effect Schema internally but needs Zod schemas for the HTTP layer, derive Zod from Schema using the `zod()` helper from `@opencode-ai/core/effect-zod`: - -```ts -import { zod } from "@opencode-ai/core/effect-zod" - -export const ZodInfo = zod(Info) // derives z.ZodType from Schema.Union -``` - -See `Auth.ZodInfo` for the canonical example. +Use Effect Schema directly at HTTP, tool, and AI SDK boundaries. For provider-facing JSON Schema, use a boundary-specific helper such as `ToolJsonSchema.fromSchema(...)`; do not reintroduce generic Effect Schema → Zod conversion. ## InstanceState init patterns diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 20b3e70e7..1fc6a4478 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -1,19 +1,16 @@ # Schema migration Practical reference for migrating data types in `packages/opencode` from -Zod-first definitions to Effect Schema with Zod compatibility shims. +Zod-first definitions to Effect Schema. ## Goal Use Effect Schema as the source of truth for domain models, IDs, inputs, -outputs, and typed errors. Keep Zod available at existing HTTP, tool, and -compatibility boundaries by exposing a `.zod` static derived from the Effect -schema via `@opencode-ai/core/effect-zod`. +outputs, and typed errors. Prefer native Effect Schema, Standard Schema, and +native JSON Schema generation at HTTP, tool, and AI SDK boundaries. -The long-term driver is `specs/effect/http-api.md` — once the HTTP server -moves to `@effect/platform`, every Schema-first DTO can flow through -`HttpApi` / `HttpRouter` without a zod translation layer, and the entire -`effect-zod` walker plus every `.zod` static can be deleted. +The long-term driver is `specs/effect/http-api.md`: Schema-first DTOs should +flow through `HttpApi` / `HttpRouter` without a Zod translation layer. ## Preferred shapes @@ -26,19 +23,16 @@ export class Info extends Schema.Class("Foo.Info")({ id: FooID, name: Schema.String, enabled: Schema.Boolean, -}) { - static readonly zod = zod(Info) -} +}) {} ``` -If the class cannot reference itself cleanly during initialization, use the -two-step `withStatics` pattern: +If a schema needs local static helpers, use the two-step `withStatics` pattern: ```ts export const Info = Schema.Struct({ id: FooID, name: Schema.String, -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}).pipe(withStatics((s) => ({ decode: Schema.decodeUnknownOption(s) }))) ``` ### Errors @@ -53,15 +47,13 @@ export class NotFoundError extends Schema.TaggedErrorClass()("Foo ### IDs and branded leaf types -Keep branded/schema-backed IDs as Effect schemas and expose -`static readonly zod` for compatibility when callers still expect Zod. +Keep branded/schema-backed IDs as Effect schemas. ### Refinements -Reuse named refinements instead of re-spelling `z.number().int().positive()` -in every schema. The `effect-zod` walker translates the Effect versions into -the corresponding zod methods, so JSON Schema output (`type: integer`, -`exclusiveMinimum`, `pattern`, `format: uuid`, …) is preserved. +Reuse named refinements instead of re-spelling numeric or string constraints in +every schema. Boundary JSON Schema helpers should normalize native Effect JSON +Schema output only where a provider requires it. ```ts const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) @@ -69,18 +61,15 @@ const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreate const HexColor = Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)) ``` -See `test/util/effect-zod.test.ts` for the full set of translated checks. - ## Compatibility rule -During migration, route validators, tool parameters, and any existing -Zod-based boundary should consume the derived `.zod` schema instead of +During migration, route validators, tool parameters, and AI SDK schemas should +consume Effect schemas directly or use a narrow boundary helper. Avoid maintaining a second hand-written Zod schema. The default should be: - Effect Schema owns the type -- `.zod` exists only as a compatibility surface - new domain models should not start Zod-first unless there is a concrete boundary-specific need @@ -89,27 +78,22 @@ The default should be: It is fine to keep a Zod-native schema temporarily when: - the type is only used at an HTTP or tool boundary and is not reused elsewhere -- the validator depends on Zod-only transforms or behavior not yet covered by `zod()` +- the validator is part of an existing public API that explicitly accepts Zod - the migration would force unrelated churn across a large call graph When this happens, prefer leaving a short note or TODO rather than silently creating a parallel schema source of truth. -## Escape hatches - -The walker in `@opencode-ai/core/effect-zod` exposes two explicit escape hatches for -cases the pure-Schema path cannot express. Each one stays in the codebase -only as long as its upstream or local dependency requires it — inline -comments document when each can be deleted. +## Boundary helpers -### `ZodOverride` annotation +Use narrow helpers at concrete boundaries instead of a generic Schema → Zod bridge. -Replaces the entire derivation with a hand-crafted zod schema. Used when: +- Tool parameters: `ToolJsonSchema.fromSchema(...)` and `ToolJsonSchema.fromTool(...)` +- Public config/TUI schemas: `packages/opencode/script/schema.ts` +- AI SDK object generation: `Schema.toStandardSchemaV1(...)` plus `Schema.toStandardJSONSchemaV1(...)` -- the target carries external `$ref` metadata (e.g. - `config/model-id.ts` points at `https://models.dev/...`) -- the target is a zod-only schema that cannot yet be expressed as Schema - (e.g. `ConfigAgent.Info`, `Log.Level`) +Plugin tools are the main remaining intentional Zod boundary because the public +plugin API exposes `tool.schema = z` and `args: z.ZodRawShape`. ### Local `DeepMutable` in `config/config.ts` @@ -133,7 +117,7 @@ Migrate in this order: 2. Exported `Info`, `Input`, `Output`, and DTO types 3. Tagged domain errors 4. Service-local internal models -5. Route and tool boundary validators that can switch to `.zod` +5. Route and tool boundary validators that can switch to native Effect Schema helpers This keeps shared types canonical first and makes boundary updates mostly mechanical. @@ -142,21 +126,18 @@ mechanical. ### `src/config/` ✅ complete -All of `packages/opencode/src/config/` has been migrated. Files that still -import `z` do so only for local `ZodOverride` bridges or for `z.ZodType` -type annotations — the `export const ` values are all Effect -Schema at source. +All of `packages/opencode/src/config/` has been migrated. The `export const +` values are all Effect Schema at source. A file is considered "done" when: - its exported schema values (`Info`, `Input`, `Event`, `Definition`, etc.) are authored as Effect Schema -- any remaining zod is either a derived compat bridge (via `zod()` / - `zodObject()`), a `z.ZodType` type annotation, or a documented - `ZodOverride` escape hatch — never a hand-written parallel source of truth +- any remaining Zod is an explicit boundary compatibility choice, not a + hand-written parallel source of truth -Files that meet this bar but still carry a compat bridge are checked off -with an inline note describing the bridge and what unblocks its removal. +Files that meet this bar but still carry a compatibility boundary are checked +off with an inline note describing the boundary and what unblocks its removal. - [x] skills, formatter, console-state, mcp, lsp, permission (leaves), model-id, command, plugin, provider - [x] server, layout @@ -361,15 +342,8 @@ piecewise. - [ ] `src/util/update-schema.ts` - [ ] `src/worktree/index.ts` -### Do-not-migrate - -- `src/util/effect-zod.ts` — the walker itself. Stays zod-importing forever - (it's what emits zod from Schema). Goes away only when the `.zod` - compatibility layer is no longer needed anywhere. - ## Notes -- Use `@opencode-ai/core/effect-zod` for all Schema → Zod conversion. - Prefer one canonical schema definition. Avoid maintaining parallel Zod and Effect definitions for the same domain type. - Keep the migration incremental. Converting the domain model first is more diff --git a/packages/opencode/specs/openapi-translation-cleanup.md b/packages/opencode/specs/openapi-translation-cleanup.md index 255c09644..5be155d1b 100644 --- a/packages/opencode/specs/openapi-translation-cleanup.md +++ b/packages/opencode/specs/openapi-translation-cleanup.md @@ -100,7 +100,7 @@ Verification: - Audit `PathParameterSchemas` and `pathParameterSchema()` in `public.ts`. - Check source schemas in files like `packages/opencode/src/session/schema.ts`, `packages/opencode/src/permission/schema.ts`, and pty schema definitions. -- Add or fix `ZodOverride` / OpenAPI-compatible annotations on branded ID schemas so generated path params include the same patterns without `public.ts` overrides. +- Add or fix OpenAPI-compatible annotations on branded ID schemas so generated path params include the same patterns without `public.ts` overrides. - Delete one path override only after generated OpenAPI is unchanged for that param. Concrete first targets: diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 54cfe4fcc..3da260ea6 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -4,8 +4,6 @@ import { EffectBridge } from "@/effect/bridge" import type { InstanceContext } from "@/project/instance" import { SessionID, MessageID } from "@/session/schema" import { Effect, Layer, Context, Schema } from "effect" -import z from "zod" -import { ZodOverride } from "@opencode-ai/core/effect-zod" import { Config } from "@/config/config" import { MCP } from "../mcp" import { Skill } from "../skill" @@ -35,12 +33,11 @@ export const Info = Schema.Struct({ model: Schema.optional(Schema.String), source: Schema.optional(Schema.Literals(["command", "mcp", "skill"])), // Some command templates are lazy promises from MCP prompt resolution. - template: Schema.Unknown.annotate({ [ZodOverride]: z.promise(z.string()).or(z.string()) }), + template: Schema.Unknown, subtask: Schema.optional(Schema.Boolean), hints: Schema.Array(Schema.String), }).annotate({ identifier: "Command" }) -// for some reason zod is inferring `string` for z.promise(z.string()).or(z.string()) so we have to manually override it export type Info = Omit, "template"> & { template: Promise | string } export function hints(template: string) { diff --git a/packages/opencode/src/config/model-id.ts b/packages/opencode/src/config/model-id.ts index 6cba3ecd2..ba763f999 100644 --- a/packages/opencode/src/config/model-id.ts +++ b/packages/opencode/src/config/model-id.ts @@ -1,13 +1,5 @@ import { Schema } from "effect" -import z from "zod" -import { ZodOverride } from "@opencode-ai/core/effect-zod" -// The original Zod schema carried an external $ref pointing at the models.dev -// JSON schema. That external reference is not a named SDK component — it is a -// literal pointer to an outside schema — so the walker cannot re-derive it -// from AST metadata. Preserve the exact original Zod via ZodOverride. -export const ConfigModelID = Schema.String.annotate({ - [ZodOverride]: z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }), -}) +export const ConfigModelID = Schema.String export type ConfigModelID = Schema.Schema.Type diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index 12ce5f581..0249721c4 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -5,7 +5,6 @@ import * as LSPClient from "./client" import path from "path" import { pathToFileURL, fileURLToPath } from "url" import * as LSPServer from "./server" -import z from "zod" import { Config } from "@/config/config" import { Flag } from "@opencode-ai/core/flag/flag" import { Process } from "@/util/process" @@ -14,7 +13,6 @@ import { Effect, Layer, Context, Schema } from "effect" import { InstanceState } from "@/effect/instance-state" import { containsPath } from "@/project/instance-context" import { NonNegativeInt } from "@opencode-ai/core/schema" -import { ZodOverride } from "@opencode-ai/core/effect-zod" const log = Log.create({ service: "lsp" }) @@ -56,9 +54,7 @@ export const Status = Schema.Struct({ id: Schema.String, name: Schema.String, root: Schema.String, - status: Schema.Literals(["connected", "error"]).annotate({ - [ZodOverride]: z.union([z.literal("connected"), z.literal("error")]), - }), + status: Schema.Literals(["connected", "error"]), }).annotate({ identifier: "LSPStatus" }) export type Status = typeof Status.Type diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts index 55272fc2f..360daf54a 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts @@ -5,8 +5,8 @@ import { InstanceState } from "@/effect/instance-state" import { MCP } from "@/mcp" import { Project } from "@/project/project" import { Session } from "@/session/session" +import { ToolJsonSchema } from "@/tool/json-schema" import { ToolRegistry } from "@/tool/registry" -import * as EffectZod from "@opencode-ai/core/effect-zod" import { Worktree } from "@/worktree" import { Effect, Option } from "effect" import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" @@ -84,7 +84,7 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper return list.map((item) => ({ id: item.id, description: item.description, - parameters: EffectZod.toJsonSchema(item.parameters), + parameters: ToolJsonSchema.fromTool(item), })) }) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 2d1d05e15..4dae82038 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -1,6 +1,5 @@ import { BusEvent } from "@/bus/bus-event" import { SessionID, MessageID, PartID } from "./schema" -import z from "zod" import { NamedError } from "@opencode-ai/core/util/error" import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai" import { LSP } from "@/lsp/lsp" @@ -55,7 +54,7 @@ export const APIError = namedSchemaError("APIError", { responseBody: Schema.optional(Schema.String), metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), }) -export type APIError = z.infer +export type APIError = Schema.Schema.Type export const ContextOverflowError = namedSchemaError("ContextOverflowError", { message: Schema.String, responseBody: Schema.optional(Schema.String), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 2de4bbd30..15246dac3 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1,6 +1,5 @@ import path from "path" import os from "os" -import * as EffectZod from "@opencode-ai/core/effect-zod" import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" import * as Log from "@opencode-ai/core/util/log" @@ -21,6 +20,7 @@ import PROMPT_PLAN from "../session/prompt/plan.txt" import BUILD_SWITCH from "../session/prompt/build-switch.txt" import MAX_STEPS from "../session/prompt/max-steps.txt" import { ToolRegistry } from "@/tool/registry" +import { ToolJsonSchema } from "@/tool/json-schema" import { MCP } from "../mcp" import { LSP } from "@/lsp/lsp" import { Flag } from "@opencode-ai/core/flag/flag" @@ -565,7 +565,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the providerID: input.model.providerID, agent: input.agent, })) { - const schema = ProviderTransform.schema(input.model, EffectZod.toJsonSchema(item.parameters)) + const schema = ProviderTransform.schema(input.model, ToolJsonSchema.fromTool(item)) tools[item.id] = tool({ description: item.description, inputSchema: jsonSchema(schema), diff --git a/packages/opencode/src/tool/json-schema.ts b/packages/opencode/src/tool/json-schema.ts new file mode 100644 index 000000000..edb43e11c --- /dev/null +++ b/packages/opencode/src/tool/json-schema.ts @@ -0,0 +1,164 @@ +import type { JSONSchema7 } from "@ai-sdk/provider" +import { JsonSchema, Schema } from "effect" +import type * as Tool from "./tool" + +type JsonObject = Record +const cache = new WeakMap() + +export function fromSchema(schema: Schema.Top): JSONSchema7 { + const cached = cache.get(schema) + if (cached) return cached + + const document = Schema.toJsonSchemaDocument(schema, { additionalProperties: true }) + const result = normalize({ + $schema: JsonSchema.META_SCHEMA_URI_DRAFT_2020_12, + ...document.schema, + ...(Object.keys(document.definitions).length > 0 ? { $defs: document.definitions } : {}), + }) + const inlined = dropDefinitionsIfResolved(inlineLocalReferences(result)) + if (!isJsonSchema(inlined)) throw new Error("tool JSON Schema helper produced a non-schema value") + cache.set(schema, inlined) + return inlined +} + +export function fromTool(tool: Tool.Def): JSONSchema7 { + return tool.jsonSchema ?? fromSchema(tool.parameters as Schema.Top) +} + +function normalize(value: unknown, options: { stripNull?: boolean } = {}): unknown { + if (Array.isArray(value)) return value.map((item) => normalize(item)) + if (!isRecord(value)) return value + + const required = Array.isArray(value.required) + ? new Set(value.required.filter((item) => typeof item === "string")) + : undefined + const schema = Object.fromEntries( + Object.entries(value).map(([key, item]) => [ + key, + key === "properties" && isRecord(item) + ? Object.fromEntries( + Object.entries(item).map(([name, property]) => [ + name, + normalize(property, { stripNull: !required?.has(name) }), + ]), + ) + : normalize(item), + ]), + ) + + if (schema.additionalProperties === true) delete schema.additionalProperties + + if (options.stripNull && Array.isArray(schema.anyOf)) { + const withoutNull = schema.anyOf.filter((item) => !isRecord(item) || item.type !== "null") + if (withoutNull.length !== schema.anyOf.length) return normalize({ ...schema, anyOf: withoutNull }) + } + + if (Array.isArray(schema.anyOf)) { + const withoutNull = schema.anyOf + const number = withoutNull.find((item) => isRecord(item) && item.type === "number") + const nonFinite = withoutNull.filter( + (item) => isRecord(item) && Array.isArray(item.enum) && item.enum.every((entry) => isNonFiniteNumber(entry)), + ) + if (number && nonFinite.length === withoutNull.length - 1) { + const { anyOf: _, ...rest } = schema + return normalize({ ...number, ...rest }) + } + + if (isEmptyStructUnion(withoutNull)) { + const { anyOf: _, ...rest } = schema + return normalize({ type: "object", properties: {}, ...rest }) + } + + if (withoutNull.length === 1 && isRecord(withoutNull[0])) { + const { anyOf: _, ...rest } = schema + return normalize({ ...withoutNull[0], ...rest }) + } + } + + if (Array.isArray(schema.allOf) && schema.allOf.every(isRecord) && canFlattenAllOf(schema.allOf, schema)) { + const { allOf, ...rest } = schema + return normalize({ ...Object.assign({}, ...allOf), ...rest }) + } + + if (schema.type === "integer" && schema.maximum === undefined) { + return { minimum: Number.MIN_SAFE_INTEGER, ...schema, maximum: Number.MAX_SAFE_INTEGER } + } + + return schema +} + +function isRecord(value: unknown): value is JsonObject { + return typeof value === "object" && value !== null && !Array.isArray(value) +} + +function isJsonSchema(value: unknown): value is JSONSchema7 { + return typeof value === "boolean" || isRecord(value) +} + +function isNonFiniteNumber(value: unknown) { + return value === "NaN" || value === "Infinity" || value === "-Infinity" +} + +function isEmptyStructUnion(items: unknown[]) { + return ( + items.length === 2 && + items.some((item) => isRecord(item) && item.type === "object" && item.properties === undefined) && + items.some((item) => isRecord(item) && item.type === "array" && item.items === undefined) + ) +} + +function canFlattenAllOf(allOf: JsonObject[], parent: JsonObject) { + const keys = new Set(Object.keys(parent).filter((key) => key !== "allOf")) + return allOf.every((item) => + Object.keys(item).every((key) => { + if (keys.has(key)) return false + keys.add(key) + return true + }), + ) +} + +function inlineLocalReferences(value: unknown, definitions?: JsonObject, seen = new Set()): unknown { + if (Array.isArray(value)) return value.map((item) => inlineLocalReferences(item, definitions, seen)) + if (!isRecord(value)) return value + + const localDefinitions = definitions ?? (isRecord(value.$defs) ? value.$defs : undefined) + if (typeof value.$ref === "string" && localDefinitions) { + const name = value.$ref.match(/^#\/\$defs\/(.+)$/)?.[1] ?? value.$ref.match(/^#\/definitions\/(.+)$/)?.[1] + if (name && !seen.has(name)) { + const target = localDefinitions[name] + if (target) { + const { $ref: _, ...rest } = value + return inlineLocalReferences( + { ...(isRecord(target) ? target : {}), ...rest }, + localDefinitions, + new Set(seen).add(name), + ) + } + } + } + + return Object.fromEntries( + Object.entries(value).map(([key, item]) => [key, inlineLocalReferences(item, localDefinitions, seen)]), + ) +} + +function dropDefinitionsIfResolved(value: unknown): unknown { + if (!isRecord(value) || hasLocalReference(value)) return value + const { $defs: _, definitions: __, ...rest } = value + return rest +} + +function hasLocalReference(value: unknown): boolean { + if (Array.isArray(value)) return value.some(hasLocalReference) + if (!isRecord(value)) return false + if ( + typeof value.$ref === "string" && + (value.$ref.startsWith("#/$defs/") || value.$ref.startsWith("#/definitions/")) + ) { + return true + } + return Object.values(value).some(hasLocalReference) +} + +export * as ToolJsonSchema from "./json-schema" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 68251c342..a7411a077 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -15,9 +15,9 @@ import { SkillTool } from "./skill" import * as Tool from "./tool" import { Config } from "@/config/config" import { type ToolContext as PluginToolContext, type ToolDefinition } from "@opencode-ai/plugin" +import type { JSONSchema7, JSONSchema7Definition } from "@ai-sdk/provider" import { Schema } from "effect" import z from "zod" -import { ZodOverride } from "@opencode-ai/core/effect-zod" import { Plugin } from "../plugin" import { Provider } from "@/provider/provider" import { ProviderID, type ModelID } from "../provider/schema" @@ -137,17 +137,19 @@ export const layer: Layer.Layer< const custom: Tool.Def[] = [] function fromPlugin(id: string, def: ToolDefinition): Tool.Def { - // Plugin tools define their args as a raw Zod shape. Wrap the - // derived Zod object in a `Schema.declare` so it slots into the - // Schema-typed framework, and annotate with `ZodOverride` so the - // walker emits the original Zod object for LLM JSON Schema. - const zodParams = z.object(def.args) - const parameters = Schema.declare((u): u is unknown => zodParams.safeParse(u).success).annotate({ - [ZodOverride]: zodParams, - }) + // Plugin tools still expose Zod args publicly; keep that compatibility + // boxed at the registry boundary and give the LLM the original JSON Schema. + const entries = Object.entries(def.args) + const allZod = entries.every((entry) => isZodType(entry[1])) + const zodParams = allZod ? z.object(def.args) : undefined + const jsonSchema = zodParams ? zodJsonSchema(zodParams) : legacyJsonSchema(entries) + const parameters = zodParams + ? Schema.declare((u): u is unknown => zodParams.safeParse(u).success) + : Schema.Unknown return { id, parameters, + jsonSchema, description: def.description, execute: (args, toolCtx) => Effect.gen(function* () { @@ -323,8 +325,13 @@ export const layer: Layer.Layer< const output = { description: tool.description, parameters: tool.parameters, + jsonSchema: tool.jsonSchema, } yield* plugin.trigger("tool.definition", { toolID: tool.id }, output) + const jsonSchema = + output.parameters === tool.parameters || output.jsonSchema !== tool.jsonSchema + ? output.jsonSchema + : undefined return { id: tool.id, description: [ @@ -335,6 +342,7 @@ export const layer: Layer.Layer< .filter(Boolean) .join("\n"), parameters: output.parameters, + jsonSchema, execute: tool.execute, formatValidationError: tool.formatValidationError, } @@ -376,4 +384,50 @@ export const defaultLayer = Layer.suspend(() => ), ) +function isZodType(value: unknown): value is z.ZodType { + return typeof value === "object" && value !== null && "_zod" in value +} + +function isJsonSchemaDefinition(value: unknown): value is JSONSchema7Definition { + return typeof value === "boolean" || (typeof value === "object" && value !== null && !Array.isArray(value)) +} + +function legacyJsonSchema(entries: [string, unknown][]): JSONSchema7 { + const properties = Object.fromEntries( + entries.filter((entry): entry is [string, JSONSchema7Definition] => isJsonSchemaDefinition(entry[1])), + ) + return { + type: "object", + properties, + required: Object.keys(properties), + } +} + +function zodJsonSchema(schema: z.ZodType): JSONSchema7 { + const result = normalizeZodJsonSchema(z.toJSONSchema(schema, { io: "input" })) + if (!isJsonSchemaObject(result)) throw new Error("plugin tool Zod schema produced a non-object JSON Schema") + const { $defs, ...rest } = result + return ( + $defs && isJsonSchemaObject($defs) ? { ...rest, definitions: $defs as JSONSchema7["definitions"] } : rest + ) as JSONSchema7 +} + +function normalizeZodJsonSchema(value: unknown): unknown { + if (Array.isArray(value)) return value.map((item) => normalizeZodJsonSchema(item)) + if (typeof value !== "object" || value === null) return value + return Object.fromEntries( + Object.entries(value) + .filter((entry) => + (entry[0] === "exclusiveMaximum" || entry[0] === "exclusiveMinimum") && typeof entry[1] === "boolean" + ? false + : true, + ) + .map(([key, item]) => [key, normalizeZodJsonSchema(item)]), + ) +} + +function isJsonSchemaObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value) +} + export * as ToolRegistry from "./registry" diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 4b9ea8774..a26422d04 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -1,4 +1,5 @@ import { Effect, Schema } from "effect" +import type { JSONSchema7 } from "@ai-sdk/provider" import type { MessageV2 } from "../session/message-v2" import type { Permission } from "../permission" import type { SessionID, MessageID } from "../session/schema" @@ -38,6 +39,7 @@ export interface Def< id: string description: string parameters: Parameters + jsonSchema?: JSONSchema7 execute(args: Schema.Schema.Type, ctx: Context): Effect.Effect> formatValidationError?(error: unknown): string } diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index d2561a130..8c2be44e9 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -12,10 +12,11 @@ const MAX_TIMEOUT = 120 * 1000 // 2 minutes export const Parameters = Schema.Struct({ url: Schema.String.annotate({ description: "The URL to fetch content from" }), format: Schema.Literals(["text", "markdown", "html"]) - .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("markdown" as const))) .annotate({ description: "The format to return the content in (text, markdown, or html). Defaults to markdown.", - }), + default: "markdown", + }) + .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("markdown" as const))), timeout: Schema.optional(Schema.Number).annotate({ description: "Optional timeout in seconds (max 120)" }), }) diff --git a/packages/opencode/src/util/named-schema-error.ts b/packages/opencode/src/util/named-schema-error.ts index d87e1dcdb..a5ff0828e 100644 --- a/packages/opencode/src/util/named-schema-error.ts +++ b/packages/opencode/src/util/named-schema-error.ts @@ -1,6 +1,4 @@ import { Schema } from "effect" -import z from "zod" -import { zod } from "@opencode-ai/core/effect-zod" /** * Create a Schema-backed NamedError-shaped class. @@ -11,22 +9,14 @@ import { zod } from "@opencode-ai/core/effect-zod" * OpenAPI/SDK output is byte-identical to the original NamedError schema. * * Preserves the existing surface: - * - static `Schema` (Zod schema of the wire shape) + * - static `Schema` (Effect schema of the wire shape) * - static `isInstance(x)` * - instance `toObject()` returning `{ name, data }` * - `new X({ ...data }, { cause })` */ export function namedSchemaError(tag: Tag, fields: Fields) { - // Wire shape matches the original NamedError output so the SDK stays stable. const dataSchema = Schema.Struct(fields) - const wire = z - .object({ - name: z.literal(tag), - data: zod(dataSchema), - }) - .meta({ ref: tag }) - - // Effect Schema for the wire shape — used by HttpApi OpenAPI generation. + // Wire shape matches the original NamedError output so the SDK stays stable. const effectSchema = Schema.Struct({ name: Schema.Literal(tag), data: dataSchema, @@ -35,7 +25,7 @@ export function namedSchemaError class NamedSchemaError extends Error { - static readonly Schema = wire + static readonly Schema = effectSchema static readonly EffectSchema = effectSchema static readonly tag = tag public static isInstance(input: unknown): input is NamedSchemaError { diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 9da45c911..22ff6cde8 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" import type { NamedError } from "@opencode-ai/core/util/error" import { APICallError } from "ai" import { setTimeout as sleep } from "node:timers/promises" -import { Effect, Layer, Schedule } from "effect" +import { Effect, Layer, Schedule, Schema } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { SessionRetry } from "../../src/session/retry" import { MessageV2 } from "../../src/session/message-v2" @@ -17,7 +17,7 @@ const retryProvider = "test" const it = testEffect(Layer.mergeAll(SessionStatus.defaultLayer, CrossSpawnSpawner.defaultLayer)) function apiError(headers?: Record): MessageV2.APIError { - return MessageV2.APIError.Schema.parse( + return Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "boom", isRetryable: true, @@ -94,7 +94,7 @@ describe("session.retry.delay", () => { const step = yield* Schedule.toStepWithMetadata( SessionRetry.policy({ provider: "test", - parse: (err) => MessageV2.APIError.Schema.parse(err), + parse: Schema.decodeUnknownSync(MessageV2.APIError.Schema), set: (info) => status.set(sessionID, { type: "retry", @@ -173,7 +173,7 @@ describe("session.retry.retryable", () => { }) test("retries 500 errors even when isRetryable is false", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Internal server error", isRetryable: false, @@ -186,7 +186,7 @@ describe("session.retry.retryable", () => { }) test("retries 502 bad gateway errors", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Bad gateway", isRetryable: false, @@ -198,7 +198,7 @@ describe("session.retry.retryable", () => { }) test("retries 503 service unavailable errors", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Service unavailable", isRetryable: false, @@ -210,7 +210,7 @@ describe("session.retry.retryable", () => { }) test("does not retry 4xx errors when isRetryable is false", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Bad request", isRetryable: false, @@ -222,7 +222,7 @@ describe("session.retry.retryable", () => { }) test("retries ZlibError decompression failures", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Response decompression failed", isRetryable: true, @@ -236,7 +236,7 @@ describe("session.retry.retryable", () => { }) test("maps free limits to Go upsell action", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Free usage exceeded", isRetryable: true, @@ -262,7 +262,7 @@ describe("session.retry.retryable", () => { }) test("maps Go subscription limits to workspace PAYG upsell", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Subscription quota exceeded. You can continue using free models.", isRetryable: true, @@ -300,7 +300,7 @@ describe("session.retry.retryable", () => { }) test("maps Go subscription limits without limit metadata", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Subscription quota exceeded. You can continue using free models.", isRetryable: true, @@ -366,7 +366,7 @@ describe("session.message-v2.fromError", () => { ) test("ECONNRESET socket error is retryable", () => { - const error = MessageV2.APIError.Schema.parse( + const error = Schema.decodeUnknownSync(MessageV2.APIError.Schema)( new MessageV2.APIError({ message: "Connection reset by server", isRetryable: true, diff --git a/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap b/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap index 601f07cb3..d6c1bc45d 100644 --- a/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap +++ b/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap @@ -45,6 +45,7 @@ Output: Creates directory 'foo'" "description": "Optional timeout in milliseconds", "exclusiveMinimum": 0, "maximum": 9007199254740991, + "minimum": -9007199254740991, "type": "integer", }, "workdir": { @@ -240,7 +241,6 @@ exports[`tool parameters JSON Schema (wire shape) question 1`] = ` "type": "string", }, }, - "ref": "QuestionOption", "required": [ "label", "description", @@ -254,7 +254,6 @@ exports[`tool parameters JSON Schema (wire shape) question 1`] = ` "type": "string", }, }, - "ref": "QuestionPrompt", "required": [ "question", "header", @@ -393,14 +392,21 @@ exports[`tool parameters JSON Schema (wire shape) webfetch 1`] = ` "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "format": { - "default": "markdown", - "description": "The format to return the content in (text, markdown, or html). Defaults to markdown.", - "enum": [ - "text", - "markdown", - "html", + "anyOf": [ + { + "default": "markdown", + "description": "The format to return the content in (text, markdown, or html). Defaults to markdown.", + "enum": [ + "text", + "markdown", + "html", + ], + "type": "string", + }, + { + "type": "null", + }, ], - "type": "string", }, "timeout": { "description": "Optional timeout in seconds (max 120)", diff --git a/packages/opencode/test/tool/parameters.test.ts b/packages/opencode/test/tool/parameters.test.ts index 17af7b983..8b2dc9a74 100644 --- a/packages/opencode/test/tool/parameters.test.ts +++ b/packages/opencode/test/tool/parameters.test.ts @@ -1,13 +1,13 @@ import { describe, expect, test } from "bun:test" import { Result, Schema } from "effect" -import { toJsonSchema } from "@opencode-ai/core/effect-zod" +import { ToolJsonSchema } from "../../src/tool/json-schema" // Each tool exports its parameters schema at module scope so this test can // import them without running the tool's Effect-based init. The JSON Schema // snapshot captures what the LLM sees; the parse assertions pin down the -// accepts/rejects contract. `toJsonSchema` is the same helper `session/ +// accepts/rejects contract. `ToolJsonSchema.fromSchema` is the same helper `session/ // prompt.ts` uses to emit tool schemas to the LLM, so the snapshots stay -// byte-identical regardless of whether a tool has migrated from zod to Schema. +// provider-compatible while tools use Effect Schema internally. import { Parameters as ApplyPatch } from "../../src/tool/apply_patch" import { Parameters as Edit } from "../../src/tool/edit" @@ -32,6 +32,8 @@ const parse = >(schema: S, input: unknown): S[ const accepts = (schema: Schema.Decoder, input: unknown): boolean => Result.isSuccess(Schema.decodeUnknownResult(schema)(input)) +const toJsonSchema = ToolJsonSchema.fromSchema + describe("tool parameters", () => { describe("JSON Schema (wire shape)", () => { test("apply_patch", () => expect(toJsonSchema(ApplyPatch)).toMatchSnapshot()) @@ -50,6 +52,36 @@ describe("tool parameters", () => { test("webfetch", () => expect(toJsonSchema(WebFetch)).toMatchSnapshot()) test("websearch", () => expect(toJsonSchema(WebSearch)).toMatchSnapshot()) test("write", () => expect(toJsonSchema(Write)).toMatchSnapshot()) + + test("inlines named child schemas for provider compatibility", () => { + const schema = toJsonSchema(Question) + expect(schema).not.toHaveProperty("$defs") + expect(schema).toMatchObject({ + properties: { + questions: { items: { properties: { options: { items: { properties: { label: { type: "string" } } } } } } }, + }, + }) + }) + + test("preserves required nullable fields", () => { + expect(toJsonSchema(Schema.Struct({ value: Schema.NullOr(Schema.String) }))).toMatchObject({ + properties: { value: { anyOf: expect.arrayContaining([{ type: "null" }]) } }, + }) + }) + + test("keeps repeated allOf constraints instead of dropping duplicates", () => { + expect( + toJsonSchema( + Schema.Struct({ value: Schema.String.check(Schema.isPattern(/^a/)).check(Schema.isPattern(/z$/)) }), + ), + ).toMatchObject({ properties: { value: { allOf: [{ pattern: "^a" }, { pattern: "z$" }] } } }) + }) + + test("bounds bare integer fields to safe integer range", () => { + expect(toJsonSchema(Schema.Struct({ value: Schema.Int }))).toMatchObject({ + properties: { value: { minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER } }, + }) + }) }) describe("apply_patch", () => { diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index dc66c308a..37cb7a43d 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -1,7 +1,8 @@ import { afterEach, describe, expect } from "bun:test" import path from "path" import fs from "fs/promises" -import { Effect, Layer } from "effect" +import { pathToFileURL } from "url" +import { Effect, Layer, Result, Schema } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { ToolRegistry } from "@/tool/registry" import { Flag } from "@opencode-ai/core/flag/flag" @@ -26,6 +27,8 @@ import { Ripgrep } from "@/file/ripgrep" import * as Truncate from "@/tool/truncate" import { InstanceState } from "@/effect/instance-state" import { Reference } from "@/reference/reference" +import { ProviderID, ModelID } from "@/provider/schema" +import { ToolJsonSchema } from "@/tool/json-schema" const node = CrossSpawnSpawner.defaultLayer const originalExperimentalScout = Flag.OPENCODE_EXPERIMENTAL_SCOUT @@ -55,7 +58,7 @@ const registryLayer = ToolRegistry.layer.pipe( Layer.provide(Truncate.defaultLayer), ) -const it = testEffect(Layer.mergeAll(registryLayer, node)) +const it = testEffect(Layer.mergeAll(registryLayer, node, Agent.defaultLayer)) afterEach(async () => { Flag.OPENCODE_EXPERIMENTAL_SCOUT = originalExperimentalScout @@ -141,6 +144,89 @@ describe("tool.registry", () => { }), ) + it.instance("loads Zod-schema custom tools with JSON Schema and validation", () => + Effect.gen(function* () { + const test = yield* TestInstance + const customTools = path.join(test.directory, ".opencode", "tools") + const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href + yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true })) + yield* Effect.promise(() => + Bun.write( + path.join(customTools, "sql.ts"), + [ + `import { tool } from ${JSON.stringify(pluginTool)}`, + "export default tool({", + " description: 'query database',", + " args: { query: tool.schema.string().describe('SQL query to execute') },", + " execute: async ({ query }) => query,", + "})", + "", + ].join("\n"), + ), + ) + + const registry = yield* ToolRegistry.Service + const loaded = (yield* registry.all()).find((tool) => tool.id === "sql") + if (!loaded) throw new Error("custom sql tool was not loaded") + expect(loaded?.jsonSchema).toMatchObject({ + type: "object", + properties: { + query: { type: "string", description: "SQL query to execute" }, + }, + required: ["query"], + }) + expect(Result.isSuccess(Schema.decodeUnknownResult(loaded.parameters)({ query: "select 1" }))).toBe(true) + expect(Result.isSuccess(Schema.decodeUnknownResult(loaded.parameters)({}))).toBe(false) + + const agents = yield* Agent.Service + const promptTools = yield* registry.tools({ + providerID: ProviderID.opencode, + modelID: ModelID.make("test"), + agent: yield* agents.get(yield* agents.defaultAgent()), + }) + const promptTool = promptTools.find((tool) => tool.id === "sql") + if (!promptTool) throw new Error("custom sql tool was not returned for prompts") + expect(ToolJsonSchema.fromTool(promptTool)).toMatchObject({ + properties: { + query: { type: "string", description: "SQL query to execute" }, + }, + required: ["query"], + }) + }), + ) + + it.instance("loads legacy JSON-schema-shaped custom tools with wire schema", () => + Effect.gen(function* () { + const test = yield* TestInstance + const tools = path.join(test.directory, ".opencode", "tools") + yield* Effect.promise(() => fs.mkdir(tools, { recursive: true })) + yield* Effect.promise(() => + Bun.write( + path.join(tools, "legacy.ts"), + [ + "export default {", + " description: 'legacy schema tool',", + " args: { text: { type: 'string', description: 'Text to render' } },", + " execute: async ({ text }) => text,", + "}", + "", + ].join("\n"), + ), + ) + + const registry = yield* ToolRegistry.Service + const loaded = (yield* registry.all()).find((tool) => tool.id === "legacy") + if (!loaded) throw new Error("legacy custom tool was not loaded") + expect(ToolJsonSchema.fromTool(loaded)).toMatchObject({ + type: "object", + properties: { + text: { type: "string", description: "Text to render" }, + }, + required: ["text"], + }) + }), + ) + it.instance("loads tools with external dependencies without crashing", () => Effect.gen(function* () { const test = yield* TestInstance diff --git a/packages/opencode/test/util/effect-zod.test.ts b/packages/opencode/test/util/effect-zod.test.ts deleted file mode 100644 index ab3923d8e..000000000 --- a/packages/opencode/test/util/effect-zod.test.ts +++ /dev/null @@ -1,754 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { Effect, Schema, SchemaGetter } from "effect" -import z from "zod" - -import { zod, ZodOverride } from "@opencode-ai/core/effect-zod" - -function json(schema: z.ZodTypeAny) { - const { $schema: _, ...rest } = z.toJSONSchema(schema) - return rest -} - -describe("util.effect-zod", () => { - test("converts class schemas for route dto shapes", () => { - class Method extends Schema.Class("ProviderAuthMethod")({ - type: Schema.Union([Schema.Literal("oauth"), Schema.Literal("api")]), - label: Schema.String, - }) {} - - const out = zod(Method) - - expect(out.meta()?.ref).toBe("ProviderAuthMethod") - expect( - out.parse({ - type: "oauth", - label: "OAuth", - }), - ).toEqual({ - type: "oauth", - label: "OAuth", - }) - }) - - test("converts structs with optional fields, arrays, and records", () => { - const out = zod( - Schema.Struct({ - foo: Schema.optional(Schema.String), - bar: Schema.Array(Schema.Number), - baz: Schema.Record(Schema.String, Schema.Boolean), - }), - ) - - expect( - out.parse({ - bar: [1, 2], - baz: { ok: true }, - }), - ).toEqual({ - bar: [1, 2], - baz: { ok: true }, - }) - expect( - out.parse({ - foo: "hi", - bar: [1], - baz: { ok: false }, - }), - ).toEqual({ - foo: "hi", - bar: [1], - baz: { ok: false }, - }) - }) - - describe("Tuples", () => { - test("fixed-length tuple parses matching array", () => { - const out = zod(Schema.Tuple([Schema.String, Schema.Number])) - expect(out.parse(["a", 1])).toEqual(["a", 1]) - expect(out.safeParse(["a"]).success).toBe(false) - expect(out.safeParse(["a", "b"]).success).toBe(false) - }) - - test("single-element tuple parses a one-element array", () => { - const out = zod(Schema.Tuple([Schema.Boolean])) - expect(out.parse([true])).toEqual([true]) - expect(out.safeParse([true, false]).success).toBe(false) - }) - - test("tuple inside a union picks the right branch", () => { - const out = zod(Schema.Union([Schema.String, Schema.Tuple([Schema.String, Schema.Number])])) - expect(out.parse("hello")).toBe("hello") - expect(out.parse(["foo", 42])).toEqual(["foo", 42]) - expect(out.safeParse(["foo"]).success).toBe(false) - }) - - test("plain arrays still work (no element positions)", () => { - const out = zod(Schema.Array(Schema.String)) - expect(out.parse(["a", "b", "c"])).toEqual(["a", "b", "c"]) - expect(out.parse([])).toEqual([]) - }) - }) - - test("string literal unions produce z.enum with enum in JSON Schema", () => { - const Action = Schema.Literals(["allow", "deny", "ask"]) - const out = zod(Action) - - expect(out.parse("allow")).toBe("allow") - expect(out.parse("deny")).toBe("deny") - expect(() => out.parse("nope")).toThrow() - - // Matches native z.enum JSON Schema output - const bridged = json(out) - const native = json(z.enum(["allow", "deny", "ask"])) - expect(bridged).toEqual(native) - expect(bridged.enum).toEqual(["allow", "deny", "ask"]) - }) - - test("ZodOverride annotation provides the Zod schema for branded IDs", () => { - const override = z.string().startsWith("per") - const ID = Schema.String.annotate({ [ZodOverride]: override }).pipe(Schema.brand("TestID")) - - const Parent = Schema.Struct({ id: ID, name: Schema.String }) - const out = zod(Parent) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect((out as any).parse({ id: "per_abc", name: "test" })).toEqual({ id: "per_abc", name: "test" }) - - const schema = json(out) as any - expect(schema.properties.id).toEqual({ type: "string", pattern: "^per.*" }) - }) - - test("Schema.Class nested in a parent preserves ref via identifier", () => { - class Inner extends Schema.Class("MyInner")({ - value: Schema.String, - }) {} - - class Outer extends Schema.Class("MyOuter")({ - inner: Inner, - }) {} - - const out = zod(Outer) - expect(out.meta()?.ref).toBe("MyOuter") - - const shape = (out as any).shape ?? (out as any)._def?.shape?.() - expect(shape.inner.meta()?.ref).toBe("MyInner") - }) - - test("Schema.Class preserves identifier and uses enum format", () => { - class Rule extends Schema.Class("PermissionRule")({ - permission: Schema.String, - pattern: Schema.String, - action: Schema.Literals(["allow", "deny", "ask"]), - }) {} - - const out = zod(Rule) - expect(out.meta()?.ref).toBe("PermissionRule") - - const schema = json(out) as any - expect(schema.properties.action).toEqual({ - type: "string", - enum: ["allow", "deny", "ask"], - }) - }) - - test("ZodOverride on ID carries pattern through Schema.Class", () => { - const ID = Schema.String.annotate({ - [ZodOverride]: z.string().startsWith("per"), - }) - - class Request extends Schema.Class("TestRequest")({ - id: ID, - name: Schema.String, - }) {} - - const schema = json(zod(Request)) as any - expect(schema.properties.id).toEqual({ type: "string", pattern: "^per.*" }) - expect(schema.properties.name).toEqual({ type: "string" }) - }) - - test("Permission schemas match original Zod equivalents", () => { - const MsgID = Schema.String.annotate({ [ZodOverride]: z.string().startsWith("msg") }) - const PerID = Schema.String.annotate({ [ZodOverride]: z.string().startsWith("per") }) - const SesID = Schema.String.annotate({ [ZodOverride]: z.string().startsWith("ses") }) - - class Tool extends Schema.Class("PermissionTool")({ - messageID: MsgID, - callID: Schema.String, - }) {} - - class Request extends Schema.Class("PermissionRequest")({ - id: PerID, - sessionID: SesID, - permission: Schema.String, - patterns: Schema.Array(Schema.String), - metadata: Schema.Record(Schema.String, Schema.Unknown), - always: Schema.Array(Schema.String), - tool: Schema.optional(Tool), - }) {} - - const bridged = json(zod(Request)) as any - expect(bridged.properties.id).toEqual({ type: "string", pattern: "^per.*" }) - expect(bridged.properties.sessionID).toEqual({ type: "string", pattern: "^ses.*" }) - expect(bridged.properties.permission).toEqual({ type: "string" }) - expect(bridged.required?.sort()).toEqual(["id", "sessionID", "permission", "patterns", "metadata", "always"].sort()) - - // Tool field is present with the ref from Schema.Class identifier - const toolSchema = json(zod(Tool)) as any - expect(toolSchema.properties.messageID).toEqual({ type: "string", pattern: "^msg.*" }) - expect(toolSchema.properties.callID).toEqual({ type: "string" }) - }) - - test("ZodOverride survives Schema.brand", () => { - const override = z.string().startsWith("ses") - const ID = Schema.String.annotate({ [ZodOverride]: override }).pipe(Schema.brand("SessionID")) - - // The branded schema's AST still has the override - class Parent extends Schema.Class("Parent")({ - sessionID: ID, - }) {} - - const schema = json(zod(Parent)) as any - expect(schema.properties.sessionID).toEqual({ type: "string", pattern: "^ses.*" }) - }) - - describe("Schema.check translation", () => { - test("filter returning string triggers refinement with that message", () => { - const isEven = Schema.makeFilter((n: number) => (n % 2 === 0 ? undefined : "expected an even number")) - const schema = zod(Schema.Number.check(isEven)) - - expect(schema.parse(4)).toBe(4) - const result = schema.safeParse(3) - expect(result.success).toBe(false) - expect(result.error!.issues[0].message).toBe("expected an even number") - }) - - test("filter returning false triggers refinement with fallback message", () => { - const nonEmpty = Schema.makeFilter((s: string) => s.length > 0) - const schema = zod(Schema.String.check(nonEmpty)) - - expect(schema.parse("hi")).toBe("hi") - const result = schema.safeParse("") - expect(result.success).toBe(false) - expect(result.error!.issues[0].message).toMatch(/./) - }) - - test("filter returning undefined passes validation", () => { - const alwaysOk = Schema.makeFilter(() => undefined) - const schema = zod(Schema.Number.check(alwaysOk)) - - expect(schema.parse(42)).toBe(42) - }) - - test("annotations.message on the filter is used when filter returns false", () => { - const positive = Schema.makeFilter((n: number) => n > 0, { message: "must be positive" }) - const schema = zod(Schema.Number.check(positive)) - - const result = schema.safeParse(-1) - expect(result.success).toBe(false) - expect(result.error!.issues[0].message).toBe("must be positive") - }) - - test("cross-field check on a record flags missing key", () => { - const hasKey = Schema.makeFilter((data: Record) => - "required" in data ? undefined : "missing 'required' key", - ) - const schema = zod(Schema.Record(Schema.String, Schema.Struct({ enabled: Schema.Boolean })).check(hasKey)) - - expect(schema.parse({ required: { enabled: true } })).toEqual({ - required: { enabled: true }, - }) - - const result = schema.safeParse({ other: { enabled: true } }) - expect(result.success).toBe(false) - expect(result.error!.issues[0].message).toBe("missing 'required' key") - }) - }) - - describe("StructWithRest / catchall", () => { - test("struct with a string-keyed record rest parses known AND extra keys", () => { - const schema = zod( - Schema.StructWithRest( - Schema.Struct({ - apiKey: Schema.optional(Schema.String), - baseURL: Schema.optional(Schema.String), - }), - [Schema.Record(Schema.String, Schema.Unknown)], - ), - ) - - // Known fields come through as declared - expect(schema.parse({ apiKey: "sk-x" })).toEqual({ apiKey: "sk-x" }) - - // Extra keys are preserved (catchall) - expect( - schema.parse({ - apiKey: "sk-x", - baseURL: "https://api.example.com", - customField: "anything", - nested: { foo: 1 }, - }), - ).toEqual({ - apiKey: "sk-x", - baseURL: "https://api.example.com", - customField: "anything", - nested: { foo: 1 }, - }) - }) - - test("catchall value type constrains the extras", () => { - const schema = zod( - Schema.StructWithRest( - Schema.Struct({ - count: Schema.Number, - }), - [Schema.Record(Schema.String, Schema.Number)], - ), - ) - - // Known field + numeric extras - expect(schema.parse({ count: 10, a: 1, b: 2 })).toEqual({ count: 10, a: 1, b: 2 }) - - // Non-numeric extra is rejected - expect(schema.safeParse({ count: 10, bad: "not a number" }).success).toBe(false) - }) - - test("JSON schema output marks additionalProperties appropriately", () => { - const schema = zod( - Schema.StructWithRest( - Schema.Struct({ - id: Schema.String, - }), - [Schema.Record(Schema.String, Schema.Unknown)], - ), - ) - const shape = json(schema) as { additionalProperties?: unknown } - // Presence of `additionalProperties` (truthy or a schema) signals catchall. - expect(shape.additionalProperties).not.toBe(false) - expect(shape.additionalProperties).toBeDefined() - }) - - test("plain struct without rest still emits additionalProperties unchanged (regression)", () => { - const schema = zod(Schema.Struct({ id: Schema.String })) - expect(schema.parse({ id: "x" })).toEqual({ id: "x" }) - }) - }) - - describe("transforms (Schema.decodeTo)", () => { - test("Number -> pseudo-Duration (seconds) applies the decode function", () => { - // Models the account/account.ts DurationFromSeconds pattern. - const SecondsToMs = Schema.Number.pipe( - Schema.decodeTo(Schema.Number, { - decode: SchemaGetter.transform((n: number) => n * 1000), - encode: SchemaGetter.transform((ms: number) => ms / 1000), - }), - ) - - const schema = zod(SecondsToMs) - expect(schema.parse(3)).toBe(3000) - expect(schema.parse(0)).toBe(0) - }) - - test("String -> Number via parseInt decode", () => { - const ParsedInt = Schema.String.pipe( - Schema.decodeTo(Schema.Number, { - decode: SchemaGetter.transform((s: string) => Number.parseInt(s, 10)), - encode: SchemaGetter.transform((n: number) => String(n)), - }), - ) - - const schema = zod(ParsedInt) - expect(schema.parse("42")).toBe(42) - expect(schema.parse("0")).toBe(0) - }) - - test("transform inside a struct field applies per-field", () => { - const Field = Schema.Number.pipe( - Schema.decodeTo(Schema.Number, { - decode: SchemaGetter.transform((n: number) => n + 1), - encode: SchemaGetter.transform((n: number) => n - 1), - }), - ) - - const schema = zod( - Schema.Struct({ - plain: Schema.Number, - bumped: Field, - }), - ) - - expect(schema.parse({ plain: 5, bumped: 10 })).toEqual({ plain: 5, bumped: 11 }) - }) - - test("chained decodeTo composes transforms in order", () => { - // String -> Number (parseInt) -> Number (doubled). - // Exercises the encoded() reduce, not just a single link. - const Chained = Schema.String.pipe( - Schema.decodeTo(Schema.Number, { - decode: SchemaGetter.transform((s: string) => Number.parseInt(s, 10)), - encode: SchemaGetter.transform((n: number) => String(n)), - }), - Schema.decodeTo(Schema.Number, { - decode: SchemaGetter.transform((n: number) => n * 2), - encode: SchemaGetter.transform((n: number) => n / 2), - }), - ) - - const schema = zod(Chained) - expect(schema.parse("21")).toBe(42) - expect(schema.parse("0")).toBe(0) - }) - - test("Schema.Class is unaffected by transform walker (returns plain object, not instance)", () => { - // Schema.Class uses Declaration + encoding under the hood to construct - // class instances. The walker must NOT apply that transform, or zod - // parsing would return class instances instead of plain objects. - class Method extends Schema.Class("TxTestMethod")({ - type: Schema.String, - value: Schema.Number, - }) {} - - const schema = zod(Method) - const parsed = schema.parse({ type: "oauth", value: 1 }) - expect(parsed).toEqual({ type: "oauth", value: 1 }) - // Guardrail: ensure we didn't get back a Method instance. - expect(parsed).not.toBeInstanceOf(Method) - }) - }) - - describe("optimizations", () => { - test("walk() memoizes by AST identity — same AST node returns same Zod", () => { - const shared = Schema.Struct({ id: Schema.String, name: Schema.String }) - const left = zod(shared) - const right = zod(shared) - expect(left).toBe(right) - }) - - test("nested reuse of the same AST reuses the cached Zod child", () => { - // Two different parents embed the same inner schema. The inner zod - // child should be identical by reference inside both parents. - class Inner extends Schema.Class("MemoTestInner")({ - value: Schema.String, - }) {} - - class OuterA extends Schema.Class("MemoTestOuterA")({ - inner: Inner, - }) {} - - class OuterB extends Schema.Class("MemoTestOuterB")({ - inner: Inner, - }) {} - - const shapeA = (zod(OuterA) as any).shape ?? (zod(OuterA) as any)._def?.shape?.() - const shapeB = (zod(OuterB) as any).shape ?? (zod(OuterB) as any)._def?.shape?.() - expect(shapeA.inner).toBe(shapeB.inner) - }) - - test("multiple checks run in a single refinement layer (all fire on one value)", () => { - // Three checks attached to the same schema. All three must run and - // report — asserting that no check silently got dropped when we - // flattened into one superRefine. - const positive = Schema.makeFilter((n: number) => (n > 0 ? undefined : "not positive")) - const even = Schema.makeFilter((n: number) => (n % 2 === 0 ? undefined : "not even")) - const under100 = Schema.makeFilter((n: number) => (n < 100 ? undefined : "too big")) - - const schema = zod(Schema.Number.check(positive).check(even).check(under100)) - - const neg = schema.safeParse(-3) - expect(neg.success).toBe(false) - expect(neg.error!.issues.map((i) => i.message)).toEqual(expect.arrayContaining(["not positive", "not even"])) - - const big = schema.safeParse(101) - expect(big.success).toBe(false) - expect(big.error!.issues.map((i) => i.message)).toContain("too big") - - // Passing value satisfies all three - expect(schema.parse(42)).toBe(42) - }) - - test("FilterGroup flattens into the single refinement layer alongside its siblings", () => { - const positive = Schema.makeFilter((n: number) => (n > 0 ? undefined : "not positive")) - const even = Schema.makeFilter((n: number) => (n % 2 === 0 ? undefined : "not even")) - const group = Schema.makeFilterGroup([positive, even]) - const under100 = Schema.makeFilter((n: number) => (n < 100 ? undefined : "too big")) - - const schema = zod(Schema.Number.check(group).check(under100)) - - const bad = schema.safeParse(-3) - expect(bad.success).toBe(false) - expect(bad.error!.issues.map((i) => i.message)).toEqual(expect.arrayContaining(["not positive", "not even"])) - }) - }) - - describe("well-known refinement translation", () => { - test("Schema.isInt emits type: integer in JSON Schema", () => { - const schema = zod(Schema.Number.check(Schema.isInt())) - const native = json(z.number().int()) - expect(json(schema)).toEqual(native) - expect(schema.parse(3)).toBe(3) - expect(schema.safeParse(1.5).success).toBe(false) - }) - - test("Schema.isGreaterThan(0) emits exclusiveMinimum: 0", () => { - const schema = zod(Schema.Number.check(Schema.isGreaterThan(0))) - expect((json(schema) as any).exclusiveMinimum).toBe(0) - expect(schema.parse(1)).toBe(1) - expect(schema.safeParse(0).success).toBe(false) - expect(schema.safeParse(-1).success).toBe(false) - }) - - test("Schema.isGreaterThanOrEqualTo(0) emits minimum: 0", () => { - const schema = zod(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))) - expect((json(schema) as any).minimum).toBe(0) - expect(schema.parse(0)).toBe(0) - expect(schema.safeParse(-1).success).toBe(false) - }) - - test("Schema.isLessThan(10) emits exclusiveMaximum: 10", () => { - const schema = zod(Schema.Number.check(Schema.isLessThan(10))) - expect((json(schema) as any).exclusiveMaximum).toBe(10) - expect(schema.parse(9)).toBe(9) - expect(schema.safeParse(10).success).toBe(false) - }) - - test("Schema.isLessThanOrEqualTo(10) emits maximum: 10", () => { - const schema = zod(Schema.Number.check(Schema.isLessThanOrEqualTo(10))) - expect((json(schema) as any).maximum).toBe(10) - expect(schema.parse(10)).toBe(10) - expect(schema.safeParse(11).success).toBe(false) - }) - - test("Schema.isMultipleOf(5) emits multipleOf: 5", () => { - const schema = zod(Schema.Number.check(Schema.isMultipleOf(5))) - expect((json(schema) as any).multipleOf).toBe(5) - expect(schema.parse(10)).toBe(10) - expect(schema.safeParse(7).success).toBe(false) - }) - - test("Schema.isFinite validates at runtime", () => { - const schema = zod(Schema.Number.check(Schema.isFinite())) - expect(schema.parse(1)).toBe(1) - expect(schema.safeParse(Infinity).success).toBe(false) - expect(schema.safeParse(NaN).success).toBe(false) - }) - - test("chained isInt + isGreaterThan(0) matches z.number().int().positive()", () => { - const schema = zod(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))) - const native = json(z.number().int().positive()) - expect(json(schema)).toEqual(native) - expect(schema.parse(3)).toBe(3) - expect(schema.safeParse(0).success).toBe(false) - expect(schema.safeParse(1.5).success).toBe(false) - }) - - test("chained isInt + isGreaterThanOrEqualTo(0) matches z.number().int().min(0)", () => { - const schema = zod(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))) - const native = json(z.number().int().min(0)) - expect(json(schema)).toEqual(native) - expect(schema.parse(0)).toBe(0) - expect(schema.safeParse(-1).success).toBe(false) - }) - - test("Schema.isBetween emits both bounds", () => { - const schema = zod(Schema.Number.check(Schema.isBetween({ minimum: 1, maximum: 10 }))) - const shape = json(schema) as any - expect(shape.minimum).toBe(1) - expect(shape.maximum).toBe(10) - expect(schema.parse(5)).toBe(5) - expect(schema.safeParse(11).success).toBe(false) - expect(schema.safeParse(0).success).toBe(false) - }) - - test("Schema.isBetween with exclusive bounds emits exclusiveMinimum/Maximum", () => { - const schema = zod( - Schema.Number.check( - Schema.isBetween({ minimum: 1, maximum: 10, exclusiveMinimum: true, exclusiveMaximum: true }), - ), - ) - const shape = json(schema) as any - expect(shape.exclusiveMinimum).toBe(1) - expect(shape.exclusiveMaximum).toBe(10) - expect(schema.parse(5)).toBe(5) - expect(schema.safeParse(1).success).toBe(false) - expect(schema.safeParse(10).success).toBe(false) - }) - - test("Schema.isInt32 (FilterGroup) produces integer bounds", () => { - const schema = zod(Schema.Number.check(Schema.isInt32())) - const shape = json(schema) as any - expect(shape.type).toBe("integer") - expect(shape.minimum).toBe(-2147483648) - expect(shape.maximum).toBe(2147483647) - expect(schema.parse(42)).toBe(42) - expect(schema.safeParse(1.5).success).toBe(false) - expect(schema.safeParse(2147483648).success).toBe(false) - }) - - test("Schema.isMinLength on string emits minLength", () => { - const schema = zod(Schema.String.check(Schema.isMinLength(3))) - expect((json(schema) as any).minLength).toBe(3) - expect(schema.parse("abc")).toBe("abc") - expect(schema.safeParse("ab").success).toBe(false) - }) - - test("Schema.isMaxLength on string emits maxLength", () => { - const schema = zod(Schema.String.check(Schema.isMaxLength(5))) - expect((json(schema) as any).maxLength).toBe(5) - expect(schema.parse("abcde")).toBe("abcde") - expect(schema.safeParse("abcdef").success).toBe(false) - }) - - test("Schema.isLengthBetween on string emits both bounds", () => { - const schema = zod(Schema.String.check(Schema.isLengthBetween(2, 4))) - const shape = json(schema) as any - expect(shape.minLength).toBe(2) - expect(shape.maxLength).toBe(4) - expect(schema.parse("abc")).toBe("abc") - expect(schema.safeParse("a").success).toBe(false) - expect(schema.safeParse("abcde").success).toBe(false) - }) - - test("Schema.isMinLength on array emits minItems", () => { - const schema = zod(Schema.Array(Schema.String).check(Schema.isMinLength(1))) - expect((json(schema) as any).minItems).toBe(1) - expect(schema.parse(["x"])).toEqual(["x"]) - expect(schema.safeParse([]).success).toBe(false) - }) - - test("Schema.isPattern emits pattern", () => { - const schema = zod(Schema.String.check(Schema.isPattern(/^per/))) - expect((json(schema) as any).pattern).toBe("^per") - expect(schema.parse("per_abc")).toBe("per_abc") - expect(schema.safeParse("abc").success).toBe(false) - }) - - test("Schema.isStartsWith matches native zod .startsWith() JSON Schema", () => { - const schema = zod(Schema.String.check(Schema.isStartsWith("per"))) - const native = json(z.string().startsWith("per")) - expect(json(schema)).toEqual(native) - expect(schema.parse("per_abc")).toBe("per_abc") - expect(schema.safeParse("abc").success).toBe(false) - }) - - test("Schema.isEndsWith matches native zod .endsWith() JSON Schema", () => { - const schema = zod(Schema.String.check(Schema.isEndsWith(".json"))) - const native = json(z.string().endsWith(".json")) - expect(json(schema)).toEqual(native) - expect(schema.parse("a.json")).toBe("a.json") - expect(schema.safeParse("a.txt").success).toBe(false) - }) - - test("Schema.isUUID emits format: uuid", () => { - const schema = zod(Schema.String.check(Schema.isUUID())) - expect((json(schema) as any).format).toBe("uuid") - }) - - test("mix of well-known and anonymous filters translates known and reroutes unknown to superRefine", () => { - // isInt is well-known (translates to .int()); the anonymous filter falls - // back to superRefine. - const notSeven = Schema.makeFilter((n: number) => (n !== 7 ? undefined : "no sevens allowed")) - const schema = zod(Schema.Number.check(Schema.isInt()).check(notSeven)) - - const shape = json(schema) as any - // Well-known translation is preserved — type is integer, not plain number - expect(shape.type).toBe("integer") - - // Runtime: both constraints fire - expect(schema.parse(3)).toBe(3) - expect(schema.safeParse(1.5).success).toBe(false) - const seven = schema.safeParse(7) - expect(seven.success).toBe(false) - expect(seven.error!.issues[0].message).toBe("no sevens allowed") - }) - - test("inside a struct field, well-known refinements propagate through", () => { - // Mirrors config.ts port: z.number().int().positive().optional() - const Port = Schema.optional(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))) - const schema = zod(Schema.Struct({ port: Port })) - const shape = json(schema) as any - expect(shape.properties.port.type).toBe("integer") - expect(shape.properties.port.exclusiveMinimum).toBe(0) - }) - }) - - describe("Schema.optionalWith defaults", () => { - test("parsing undefined returns the default value", () => { - const schema = zod( - Schema.Struct({ - mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))), - }), - ) - expect(schema.parse({})).toEqual({ mode: "ctrl-x" }) - expect(schema.parse({ mode: undefined })).toEqual({ mode: "ctrl-x" }) - }) - - test("parsing a real value returns that value (default does not fire)", () => { - const schema = zod( - Schema.Struct({ - mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))), - }), - ) - expect(schema.parse({ mode: "ctrl-y" })).toEqual({ mode: "ctrl-y" }) - }) - - test("default on a number field", () => { - const schema = zod( - Schema.Struct({ - count: Schema.Number.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(42))), - }), - ) - expect(schema.parse({})).toEqual({ count: 42 }) - expect(schema.parse({ count: 7 })).toEqual({ count: 7 }) - }) - - test("multiple defaulted fields inside a struct", () => { - const schema = zod( - Schema.Struct({ - leader: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))), - quit: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-c"))), - inner: Schema.String, - }), - ) - expect(schema.parse({ inner: "hi" })).toEqual({ - leader: "ctrl-x", - quit: "ctrl-c", - inner: "hi", - }) - expect(schema.parse({ leader: "a", quit: "b", inner: "c" })).toEqual({ - leader: "a", - quit: "b", - inner: "c", - }) - }) - - test("JSON Schema output includes the default key", () => { - const schema = zod( - Schema.Struct({ - mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))), - }), - ) - const shape = json(schema) as any - expect(shape.properties.mode.default).toBe("ctrl-x") - }) - - test("default referencing a computed value resolves when evaluated", () => { - // Simulates `keybinds.ts` style of per-platform defaults: the default is - // produced by an Effect that computes a value at decode time. - const platform = "darwin" - const fallback = platform === "darwin" ? "cmd-k" : "ctrl-k" - const schema = zod( - Schema.Struct({ - command_palette: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.sync(() => fallback))), - }), - ) - expect(schema.parse({})).toEqual({ command_palette: "cmd-k" }) - const shape = json(schema) as any - expect(shape.properties.command_palette.default).toBe("cmd-k") - }) - - test("plain Schema.optional (no default) still emits .optional() (regression)", () => { - const schema = zod(Schema.Struct({ foo: Schema.optional(Schema.String) })) - expect(schema.parse({})).toEqual({}) - expect(schema.parse({ foo: "hi" })).toEqual({ foo: "hi" }) - }) - }) -}) From 8030a6c1873d73491601c08616fdf87bc3159cc8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 21:31:48 -0400 Subject: [PATCH 066/378] Emit LLM stream lifecycle events (#26971) --- .../llm/src/protocols/anthropic-messages.ts | 102 ++++++++++++------ .../llm/src/protocols/bedrock-converse.ts | 92 ++++++++++++---- packages/llm/src/protocols/gemini.ts | 26 +++-- packages/llm/src/protocols/openai-chat.ts | 18 +++- .../llm/src/protocols/openai-responses.ts | 78 +++++++++----- packages/llm/src/protocols/utils/lifecycle.ts | 88 +++++++++++++++ .../llm/src/protocols/utils/tool-stream.ts | 62 ++++++++--- packages/llm/src/tool-runtime.ts | 4 +- .../test/provider/anthropic-messages.test.ts | 40 +++++-- packages/llm/test/provider/gemini.test.ts | 84 ++++++++++----- .../llm/test/provider/openai-chat.test.ts | 56 ++++++---- .../test/provider/openai-responses.test.ts | 78 +++++++++----- packages/llm/test/tool-runtime.test.ts | 9 +- packages/llm/test/tool-stream.test.ts | 19 +++- 14 files changed, 560 insertions(+), 196 deletions(-) create mode 100644 packages/llm/src/protocols/utils/lifecycle.ts diff --git a/packages/llm/src/protocols/anthropic-messages.ts b/packages/llm/src/protocols/anthropic-messages.ts index d893888fd..e27af1842 100644 --- a/packages/llm/src/protocols/anthropic-messages.ts +++ b/packages/llm/src/protocols/anthropic-messages.ts @@ -17,6 +17,7 @@ import { } from "../schema" import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared" import * as Cache from "./utils/cache" +import { Lifecycle } from "./utils/lifecycle" import { ToolStream } from "./utils/tool-stream" const ADAPTER = "anthropic-messages" @@ -190,6 +191,7 @@ type AnthropicEvent = Schema.Schema.Type interface ParserState { readonly tools: ToolStream.State readonly usage?: Usage + readonly lifecycle: Lifecycle.State } const invalid = ProviderShared.invalidRequest @@ -500,37 +502,45 @@ const onContentBlockStart = (state: ParserState, event: AnthropicEvent): StepRes if (!block) return [state, NO_EVENTS] if ((block.type === "tool_use" || block.type === "server_tool_use") && event.index !== undefined) { + const events: LLMEvent[] = [] + const lifecycle = Lifecycle.stepStart(state.lifecycle, events) return [ { ...state, + lifecycle, tools: ToolStream.start(state.tools, event.index, { id: block.id ?? String(event.index), name: block.name ?? "", providerExecuted: block.type === "server_tool_use", }), }, - NO_EVENTS, + [...events, LLMEvent.toolInputStart({ id: block.id ?? String(event.index), name: block.name ?? "" })], ] } if (block.type === "text" && block.text) { - return [state, [LLMEvent.textDelta({ id: `text-${event.index ?? 0}`, text: block.text })]] + const events: LLMEvent[] = [] + return [ + { ...state, lifecycle: Lifecycle.textDelta(state.lifecycle, events, `text-${event.index ?? 0}`, block.text) }, + events, + ] } if (block.type === "thinking" && block.thinking) { + const events: LLMEvent[] = [] return [ - state, - [ - LLMEvent.reasoningDelta({ - id: `reasoning-${event.index ?? 0}`, - text: block.thinking, - }), - ], + { + ...state, + lifecycle: Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${event.index ?? 0}`, block.thinking), + }, + events, ] } const result = serverToolResultEvent(block) - return [state, result ? [result] : NO_EVENTS] + if (!result) return [state, NO_EVENTS] + const events: LLMEvent[] = [] + return [{ ...state, lifecycle: Lifecycle.stepStart(state.lifecycle, events) }, [...events, result]] } const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(function* ( @@ -540,25 +550,37 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f const delta = event.delta if (delta?.type === "text_delta" && delta.text) { - return [state, [LLMEvent.textDelta({ id: `text-${event.index ?? 0}`, text: delta.text })]] satisfies StepResult + const events: LLMEvent[] = [] + return [ + { ...state, lifecycle: Lifecycle.textDelta(state.lifecycle, events, `text-${event.index ?? 0}`, delta.text) }, + events, + ] satisfies StepResult } if (delta?.type === "thinking_delta" && delta.thinking) { + const events: LLMEvent[] = [] return [ - state, - [LLMEvent.reasoningDelta({ id: `reasoning-${event.index ?? 0}`, text: delta.thinking })], + { + ...state, + lifecycle: Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${event.index ?? 0}`, delta.thinking), + }, + events, ] satisfies StepResult } if (delta?.type === "signature_delta" && delta.signature) { + const events: LLMEvent[] = [] return [ - state, - [ - LLMEvent.reasoningEnd({ - id: `reasoning-${event.index ?? 0}`, - providerMetadata: anthropicMetadata({ signature: delta.signature }), - }), - ], + { + ...state, + lifecycle: Lifecycle.reasoningEnd( + state.lifecycle, + events, + `reasoning-${event.index ?? 0}`, + anthropicMetadata({ signature: delta.signature }), + ), + }, + events, ] satisfies StepResult } @@ -572,7 +594,10 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f "Anthropic Messages tool argument delta is missing its tool call", ) if (ToolStream.isError(result)) return yield* result - return [{ ...state, tools: result.tools }, result.event ? [result.event] : NO_EVENTS] satisfies StepResult + const events: LLMEvent[] = [] + const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle + events.push(...result.events) + return [{ ...state, lifecycle, tools: result.tools }, events] satisfies StepResult } return [state, NO_EVENTS] satisfies StepResult @@ -584,23 +609,30 @@ const onContentBlockStop = Effect.fn("AnthropicMessages.onContentBlockStop")(fun ) { if (event.index === undefined) return [state, NO_EVENTS] satisfies StepResult const result = yield* ToolStream.finish(ADAPTER, state.tools, event.index) - return [{ ...state, tools: result.tools }, result.event ? [result.event] : NO_EVENTS] satisfies StepResult + const events: LLMEvent[] = [] + const resultEvents = result.events ?? [] + const lifecycle = resultEvents.length + ? Lifecycle.stepStart(state.lifecycle, events) + : Lifecycle.reasoningEnd( + Lifecycle.textEnd(state.lifecycle, events, `text-${event.index}`), + events, + `reasoning-${event.index}`, + ) + events.push(...resultEvents) + return [{ ...state, lifecycle, tools: result.tools }, events] satisfies StepResult }) const onMessageDelta = (state: ParserState, event: AnthropicEvent): StepResult => { const usage = mergeUsage(state.usage, mapUsage(event.usage)) - return [ - { ...state, usage }, - [ - LLMEvent.requestFinish({ - reason: mapFinishReason(event.delta?.stop_reason), - usage, - providerMetadata: event.delta?.stop_sequence - ? anthropicMetadata({ stopSequence: event.delta.stop_sequence }) - : undefined, - }), - ], - ] + const events: LLMEvent[] = [] + const lifecycle = Lifecycle.finish(state.lifecycle, events, { + reason: mapFinishReason(event.delta?.stop_reason), + usage, + providerMetadata: event.delta?.stop_sequence + ? anthropicMetadata({ stopSequence: event.delta.stop_sequence }) + : undefined, + }) + return [{ ...state, lifecycle, usage }, events] } const onError = (state: ParserState, event: AnthropicEvent): StepResult => [ @@ -634,7 +666,7 @@ export const protocol = Protocol.make({ }, stream: { event: Protocol.jsonEvent(AnthropicEvent), - initial: () => ({ tools: ToolStream.empty() }), + initial: () => ({ tools: ToolStream.empty(), lifecycle: Lifecycle.initial() }), step, }, }) diff --git a/packages/llm/src/protocols/bedrock-converse.ts b/packages/llm/src/protocols/bedrock-converse.ts index f561a6d7c..7f5647c4a 100644 --- a/packages/llm/src/protocols/bedrock-converse.ts +++ b/packages/llm/src/protocols/bedrock-converse.ts @@ -17,6 +17,7 @@ import { JsonObject, optionalArray, ProviderShared } from "./shared" import { BedrockAuth, type Credentials as BedrockCredentials } from "./utils/bedrock-auth" import { BedrockCache } from "./utils/bedrock-cache" import { BedrockMedia } from "./utils/bedrock-media" +import { Lifecycle } from "./utils/lifecycle" import { ToolStream } from "./utils/tool-stream" const ADAPTER = "bedrock-converse" @@ -420,45 +421,64 @@ interface ParserState { // `metadata` (carries usage). Hold the terminal event in state so `onHalt` // can emit exactly one finish after both chunks have had a chance to arrive. readonly pendingFinish: { readonly reason: FinishReason; readonly usage?: Usage } | undefined + readonly hasToolCalls: boolean + readonly lifecycle: Lifecycle.State } const step = (state: ParserState, event: BedrockEvent) => Effect.gen(function* () { if (event.contentBlockStart?.start?.toolUse) { const index = event.contentBlockStart.contentBlockIndex + const events: LLMEvent[] = [] + const lifecycle = Lifecycle.stepStart(state.lifecycle, events) return [ { ...state, + lifecycle, tools: ToolStream.start(state.tools, index, { id: event.contentBlockStart.start.toolUse.toolUseId, name: event.contentBlockStart.start.toolUse.name, }), }, - [], + [ + ...events, + LLMEvent.toolInputStart({ + id: event.contentBlockStart.start.toolUse.toolUseId, + name: event.contentBlockStart.start.toolUse.name, + }), + ], ] as const } if (event.contentBlockDelta?.delta?.text) { + const events: LLMEvent[] = [] return [ - state, - [ - LLMEvent.textDelta({ - id: `text-${event.contentBlockDelta.contentBlockIndex}`, - text: event.contentBlockDelta.delta.text, - }), - ], + { + ...state, + lifecycle: Lifecycle.textDelta( + state.lifecycle, + events, + `text-${event.contentBlockDelta.contentBlockIndex}`, + event.contentBlockDelta.delta.text, + ), + }, + events, ] as const } if (event.contentBlockDelta?.delta?.reasoningContent?.text) { + const events: LLMEvent[] = [] return [ - state, - [ - LLMEvent.reasoningDelta({ - id: `reasoning-${event.contentBlockDelta.contentBlockIndex}`, - text: event.contentBlockDelta.delta.reasoningContent.text, - }), - ], + { + ...state, + lifecycle: Lifecycle.reasoningDelta( + state.lifecycle, + events, + `reasoning-${event.contentBlockDelta.contentBlockIndex}`, + event.contentBlockDelta.delta.reasoningContent.text, + ), + }, + events, ] as const } @@ -472,12 +492,33 @@ const step = (state: ParserState, event: BedrockEvent) => "Bedrock Converse tool delta is missing its tool call", ) if (ToolStream.isError(result)) return yield* result - return [{ ...state, tools: result.tools }, result.event ? [result.event] : []] as const + const events: LLMEvent[] = [] + const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle + events.push(...result.events) + return [{ ...state, lifecycle, tools: result.tools }, events] as const } if (event.contentBlockStop) { const result = yield* ToolStream.finish(ADAPTER, state.tools, event.contentBlockStop.contentBlockIndex) - return [{ ...state, tools: result.tools }, result.event ? [result.event] : []] as const + const events: LLMEvent[] = [] + const resultEvents = result.events ?? [] + const lifecycle = resultEvents.length + ? Lifecycle.stepStart(state.lifecycle, events) + : Lifecycle.reasoningEnd( + Lifecycle.textEnd(state.lifecycle, events, `text-${event.contentBlockStop.contentBlockIndex}`), + events, + `reasoning-${event.contentBlockStop.contentBlockIndex}`, + ) + events.push(...resultEvents) + return [ + { + ...state, + hasToolCalls: resultEvents.some(LLMEvent.is.toolCall) ? true : state.hasToolCalls, + lifecycle, + tools: result.tools, + }, + events, + ] as const } if (event.messageStop) { @@ -517,7 +558,15 @@ const framing = BedrockEventStream.framing(ADAPTER) const onHalt = (state: ParserState): ReadonlyArray => state.pendingFinish - ? [LLMEvent.requestFinish({ reason: state.pendingFinish.reason, usage: state.pendingFinish.usage })] + ? (() => { + const events: LLMEvent[] = [] + Lifecycle.finish(state.lifecycle, events, { + reason: + state.pendingFinish.reason === "stop" && state.hasToolCalls ? "tool-calls" : state.pendingFinish.reason, + usage: state.pendingFinish.usage, + }) + return events + })() : [] // ============================================================================= @@ -535,7 +584,12 @@ export const protocol = Protocol.make({ }, stream: { event: BedrockEvent, - initial: () => ({ tools: ToolStream.empty(), pendingFinish: undefined }), + initial: () => ({ + tools: ToolStream.empty(), + pendingFinish: undefined, + hasToolCalls: false, + lifecycle: Lifecycle.initial(), + }), step, onHalt, }, diff --git a/packages/llm/src/protocols/gemini.ts b/packages/llm/src/protocols/gemini.ts index 0ee88f3be..6e0b82abb 100644 --- a/packages/llm/src/protocols/gemini.ts +++ b/packages/llm/src/protocols/gemini.ts @@ -16,6 +16,7 @@ import { } from "../schema" import { JsonObject, optionalArray, ProviderShared } from "./shared" import { GeminiToolSchema } from "./utils/gemini-tool-schema" +import { Lifecycle } from "./utils/lifecycle" const ADAPTER = "gemini" export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta" @@ -134,10 +135,9 @@ interface ParserState { readonly hasToolCalls: boolean readonly nextToolCallId: number readonly usage?: Usage + readonly lifecycle: Lifecycle.State } -const invalid = ProviderShared.invalidRequest - const mediaData = ProviderShared.mediaBytes // ============================================================================= @@ -324,7 +324,14 @@ const mapFinishReason = (finishReason: string | undefined, hasToolCalls: boolean const finish = (state: ParserState): ReadonlyArray => state.finishReason || state.usage - ? [LLMEvent.requestFinish({ reason: mapFinishReason(state.finishReason, state.hasToolCalls), usage: state.usage })] + ? (() => { + const events: LLMEvent[] = [] + Lifecycle.finish(state.lifecycle, events, { + reason: mapFinishReason(state.finishReason, state.hasToolCalls), + usage: state.usage, + }) + return events + })() : [] const step = (state: ParserState, event: GeminiEvent) => { @@ -341,21 +348,21 @@ const step = (state: ParserState, event: GeminiEvent) => { const events: LLMEvent[] = [] let hasToolCalls = nextState.hasToolCalls + let lifecycle = nextState.lifecycle let nextToolCallId = nextState.nextToolCallId for (const part of candidate.content.parts) { if ("text" in part && part.text.length > 0) { - events.push( - part.thought - ? LLMEvent.reasoningDelta({ id: "reasoning-0", text: part.text }) - : LLMEvent.textDelta({ id: "text-0", text: part.text }), - ) + lifecycle = part.thought + ? Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", part.text) + : Lifecycle.textDelta(lifecycle, events, "text-0", part.text) continue } if ("functionCall" in part) { const input = part.functionCall.args const id = `tool_${nextToolCallId++}` + lifecycle = Lifecycle.stepStart(lifecycle, events) events.push(LLMEvent.toolCall({ id, name: part.functionCall.name, input })) hasToolCalls = true } @@ -365,6 +372,7 @@ const step = (state: ParserState, event: GeminiEvent) => { { ...nextState, hasToolCalls, + lifecycle, nextToolCallId, finishReason: candidate.finishReason ?? nextState.finishReason, }, @@ -388,7 +396,7 @@ export const protocol = Protocol.make({ }, stream: { event: Protocol.jsonEvent(GeminiEvent), - initial: () => ({ hasToolCalls: false, nextToolCallId: 0 }), + initial: () => ({ hasToolCalls: false, nextToolCallId: 0, lifecycle: Lifecycle.initial() }), step, onHalt: finish, }, diff --git a/packages/llm/src/protocols/openai-chat.ts b/packages/llm/src/protocols/openai-chat.ts index 133adb503..470a1473c 100644 --- a/packages/llm/src/protocols/openai-chat.ts +++ b/packages/llm/src/protocols/openai-chat.ts @@ -16,6 +16,7 @@ import { } from "../schema" import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared" import { OpenAIOptions } from "./utils/openai-options" +import { Lifecycle } from "./utils/lifecycle" import { ToolStream } from "./utils/tool-stream" const ADAPTER = "openai-chat" @@ -147,6 +148,7 @@ interface ParserState { readonly toolCallEvents: ReadonlyArray readonly usage?: Usage readonly finishReason?: FinishReason + readonly lifecycle: Lifecycle.State } const invalid = ProviderShared.invalidRequest @@ -321,7 +323,9 @@ const step = (state: ParserState, event: OpenAIChatEvent) => const toolDeltas = delta?.tool_calls ?? [] let tools = state.tools - if (delta?.content) events.push(LLMEvent.textDelta({ id: "text-0", text: delta.content })) + let lifecycle = state.lifecycle + + if (delta?.content) lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content) for (const tool of toolDeltas) { const result = ToolStream.appendOrStart( @@ -333,7 +337,8 @@ const step = (state: ParserState, event: OpenAIChatEvent) => ) if (ToolStream.isError(result)) return yield* result tools = result.tools - if (result.event) events.push(result.event) + if (result.events.length) lifecycle = Lifecycle.stepStart(lifecycle, events) + events.push(...result.events) } // Finalize accumulated tool inputs eagerly when finish_reason arrives so @@ -349,15 +354,20 @@ const step = (state: ParserState, event: OpenAIChatEvent) => toolCallEvents: finished?.events ?? state.toolCallEvents, usage, finishReason, + lifecycle, }, events, ] as const }) const finishEvents = (state: ParserState): ReadonlyArray => { + const events: LLMEvent[] = [] const hasToolCalls = state.toolCallEvents.length > 0 const reason = state.finishReason === "stop" && hasToolCalls ? "tool-calls" : state.finishReason - return [...state.toolCallEvents, ...(reason ? [LLMEvent.requestFinish({ reason, usage: state.usage })] : [])] + const lifecycle = state.toolCallEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle + events.push(...state.toolCallEvents) + if (reason) Lifecycle.finish(lifecycle, events, { reason, usage: state.usage }) + return events } // ============================================================================= @@ -377,7 +387,7 @@ export const protocol = Protocol.make({ }, stream: { event: Protocol.jsonEvent(OpenAIChatEvent), - initial: () => ({ tools: ToolStream.empty(), toolCallEvents: [] }), + initial: () => ({ tools: ToolStream.empty(), toolCallEvents: [], lifecycle: Lifecycle.initial() }), step, onHalt: finishEvents, }, diff --git a/packages/llm/src/protocols/openai-responses.ts b/packages/llm/src/protocols/openai-responses.ts index 035cc0771..e31a42cd5 100644 --- a/packages/llm/src/protocols/openai-responses.ts +++ b/packages/llm/src/protocols/openai-responses.ts @@ -17,6 +17,7 @@ import { } from "../schema" import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared" import { OpenAIOptions } from "./utils/openai-options" +import { Lifecycle } from "./utils/lifecycle" import { ToolStream } from "./utils/tool-stream" const ADAPTER = "openai-responses" @@ -165,6 +166,7 @@ type OpenAIResponsesEvent = Schema.Schema.Type interface ParserState { readonly tools: ToolStream.State readonly hasFunctionCall: boolean + readonly lifecycle: Lifecycle.State } const invalid = ProviderShared.invalidRequest @@ -385,23 +387,32 @@ const TERMINAL_TYPES = new Set(["response.completed", "response.incomplete", "re const onOutputTextDelta = (state: ParserState, event: OpenAIResponsesEvent): StepResult => { if (!event.delta) return [state, NO_EVENTS] - return [state, [LLMEvent.textDelta({ id: event.item_id ?? "text-0", text: event.delta })]] + const events: LLMEvent[] = [] + return [ + { ...state, lifecycle: Lifecycle.textDelta(state.lifecycle, events, event.item_id ?? "text-0", event.delta) }, + events, + ] } const onOutputItemAdded = (state: ParserState, event: OpenAIResponsesEvent): StepResult => { const item = event.item if (item?.type !== "function_call" || !item.id) return [state, NO_EVENTS] + const providerMetadata = openaiMetadata({ itemId: item.id }) + const events: LLMEvent[] = [] + const lifecycle = Lifecycle.stepStart(state.lifecycle, events) return [ { + ...state, + lifecycle, hasFunctionCall: state.hasFunctionCall, tools: ToolStream.start(state.tools, item.id, { id: item.call_id ?? item.id, name: item.name ?? "", input: item.arguments ?? "", - providerMetadata: openaiMetadata({ itemId: item.id }), + providerMetadata, }), }, - NO_EVENTS, + [...events, LLMEvent.toolInputStart({ id: item.call_id ?? item.id, name: item.name ?? "", providerMetadata })], ] } @@ -418,10 +429,10 @@ const onFunctionCallArgumentsDelta = Effect.fn("OpenAIResponses.onFunctionCallAr "OpenAI Responses tool argument delta is missing its tool call", ) if (ToolStream.isError(result)) return yield* result - return [ - { hasFunctionCall: state.hasFunctionCall, tools: result.tools }, - result.event ? [result.event] : NO_EVENTS, - ] satisfies StepResult + const events: LLMEvent[] = [] + const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle + events.push(...result.events) + return [{ ...state, lifecycle, tools: result.tools }, events] satisfies StepResult }) const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function* ( @@ -440,33 +451,46 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function* item.arguments === undefined ? yield* ToolStream.finish(ADAPTER, tools, item.id) : yield* ToolStream.finishWithInput(ADAPTER, tools, item.id, item.arguments) + const events: LLMEvent[] = [] + const resultEvents = result.events ?? [] + const lifecycle = resultEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle + events.push(...resultEvents) return [ - { hasFunctionCall: result.event ? true : state.hasFunctionCall, tools: result.tools }, - result.event ? [result.event] : NO_EVENTS, + { + ...state, + lifecycle, + hasFunctionCall: resultEvents.some(LLMEvent.is.toolCall) ? true : state.hasFunctionCall, + tools: result.tools, + }, + events, ] satisfies StepResult } - if (isHostedToolItem(item)) return [state, hostedToolEvents(item)] satisfies StepResult + if (isHostedToolItem(item)) { + const events: LLMEvent[] = [] + const lifecycle = Lifecycle.stepStart(state.lifecycle, events) + events.push(...hostedToolEvents(item)) + return [{ ...state, lifecycle }, events] satisfies StepResult + } return [state, NO_EVENTS] satisfies StepResult }) -const onResponseFinish = (state: ParserState, event: OpenAIResponsesEvent): StepResult => [ - state, - [ - LLMEvent.requestFinish({ - reason: mapFinishReason(event, state.hasFunctionCall), - usage: mapUsage(event.response?.usage), - providerMetadata: - event.response?.id || event.response?.service_tier - ? openaiMetadata({ - responseId: event.response.id, - serviceTier: event.response.service_tier, - }) - : undefined, - }), - ], -] +const onResponseFinish = (state: ParserState, event: OpenAIResponsesEvent): StepResult => { + const events: LLMEvent[] = [] + const lifecycle = Lifecycle.finish(state.lifecycle, events, { + reason: mapFinishReason(event, state.hasFunctionCall), + usage: mapUsage(event.response?.usage), + providerMetadata: + event.response?.id || event.response?.service_tier + ? openaiMetadata({ + responseId: event.response.id, + serviceTier: event.response.service_tier, + }) + : undefined, + }) + return [{ ...state, lifecycle }, events] +} const onResponseFailed = (state: ParserState, event: OpenAIResponsesEvent): StepResult => [ state, @@ -506,7 +530,7 @@ export const protocol = Protocol.make({ }, stream: { event: Protocol.jsonEvent(OpenAIResponsesEvent), - initial: () => ({ hasFunctionCall: false, tools: ToolStream.empty() }), + initial: () => ({ hasFunctionCall: false, tools: ToolStream.empty(), lifecycle: Lifecycle.initial() }), step, terminal: (event) => TERMINAL_TYPES.has(event.type), }, diff --git a/packages/llm/src/protocols/utils/lifecycle.ts b/packages/llm/src/protocols/utils/lifecycle.ts new file mode 100644 index 000000000..67039b137 --- /dev/null +++ b/packages/llm/src/protocols/utils/lifecycle.ts @@ -0,0 +1,88 @@ +import { LLMEvent, type FinishReason, type ProviderMetadata, type Usage } from "../../schema" + +export interface State { + readonly stepStarted: boolean + readonly text: ReadonlySet + readonly reasoning: ReadonlySet +} + +export const initial = (): State => ({ stepStarted: false, text: new Set(), reasoning: new Set() }) + +export const stepStart = (state: State, events: LLMEvent[]): State => { + if (state.stepStarted) return state + events.push(LLMEvent.stepStart({ index: 0 })) + return { ...state, stepStarted: true } +} + +export const textDelta = (state: State, events: LLMEvent[], id: string, text: string): State => { + const stepped = stepStart(state, events) + if (stepped.text.has(id)) { + events.push(LLMEvent.textDelta({ id, text })) + return stepped + } + events.push(LLMEvent.textStart({ id }), LLMEvent.textDelta({ id, text })) + return { ...stepped, text: new Set([...stepped.text, id]) } +} + +export const reasoningDelta = (state: State, events: LLMEvent[], id: string, text: string): State => { + const stepped = stepStart(state, events) + if (stepped.reasoning.has(id)) { + events.push(LLMEvent.reasoningDelta({ id, text })) + return stepped + } + events.push(LLMEvent.reasoningStart({ id }), LLMEvent.reasoningDelta({ id, text })) + return { ...stepped, reasoning: new Set([...stepped.reasoning, id]) } +} + +export const reasoningEnd = ( + state: State, + events: LLMEvent[], + id: string, + providerMetadata?: ProviderMetadata, +): State => { + if (!state.reasoning.has(id)) return state + const stepped = stepStart(state, events) + events.push(LLMEvent.reasoningEnd({ id, providerMetadata })) + const reasoning = new Set(stepped.reasoning) + reasoning.delete(id) + return { ...stepped, reasoning } +} + +export const textEnd = (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata): State => { + if (!state.text.has(id)) return state + const stepped = stepStart(state, events) + events.push(LLMEvent.textEnd({ id, providerMetadata })) + const text = new Set(stepped.text) + text.delete(id) + return { ...stepped, text } +} + +const closeOpenBlocks = (state: State, events: LLMEvent[]): State => { + for (const id of state.reasoning) events.push(LLMEvent.reasoningEnd({ id })) + for (const id of state.text) events.push(LLMEvent.textEnd({ id })) + return { ...state, text: new Set(), reasoning: new Set() } +} + +export const finish = ( + state: State, + events: LLMEvent[], + input: { + readonly reason: FinishReason + readonly usage?: Usage + readonly providerMetadata?: ProviderMetadata + }, +): State => { + const stepped = closeOpenBlocks(stepStart(state, events), events) + events.push( + LLMEvent.stepFinish({ + index: 0, + reason: input.reason, + usage: input.usage, + providerMetadata: input.providerMetadata, + }), + LLMEvent.requestFinish(input), + ) + return { ...stepped, stepStarted: false } +} + +export * as Lifecycle from "./lifecycle" diff --git a/packages/llm/src/protocols/utils/tool-stream.ts b/packages/llm/src/protocols/utils/tool-stream.ts index aa9c70f01..8e07a64bf 100644 --- a/packages/llm/src/protocols/utils/tool-stream.ts +++ b/packages/llm/src/protocols/utils/tool-stream.ts @@ -1,5 +1,5 @@ import { Effect } from "effect" -import { LLMError, LLMEvent, type ProviderMetadata, type ToolCall, type ToolInputDelta } from "../../schema" +import { LLMError, LLMEvent, type ProviderMetadata, type ToolCall } from "../../schema" import { eventError, parseToolInput, type ToolAccumulator } from "../shared" type StreamKey = string | number @@ -27,13 +27,13 @@ export type State = Partial> /** * Result of adding argument text to one pending tool call. It returns both the * next `tools` state and the updated `tool` because parsers often need the - * current id/name immediately. `event` is present only when new text arrived; - * metadata-only deltas update identity without emitting `tool-input-delta`. + * current id/name immediately. `events` contains lifecycle and delta events + * produced by the append; metadata-only deltas update identity without output. */ export interface AppendOutcome { readonly tools: State readonly tool: PendingTool - readonly event?: ToolInputDelta + readonly events: ReadonlyArray } /** Create empty accumulator state for one provider stream. */ @@ -49,7 +49,14 @@ const withoutTool = (tools: State, key: K): State => return next } -const inputDelta = (tool: PendingTool, text: string): ToolInputDelta => +const inputStart = (tool: PendingTool) => + LLMEvent.toolInputStart({ + id: tool.id, + name: tool.name, + providerMetadata: tool.providerMetadata, + }) + +const inputDelta = (tool: PendingTool, text: string) => LLMEvent.toolInputDelta({ id: tool.id, name: tool.name, @@ -76,11 +83,16 @@ const appendTool = ( key: K, tool: PendingTool, text: string, -): AppendOutcome => ({ - tools: withTool(tools, key, tool), - tool, - event: text.length === 0 ? undefined : inputDelta(tool, text), -}) +): AppendOutcome => { + const events: LLMEvent[] = [] + if (!tools[key]) events.push(inputStart(tool)) + if (text.length > 0) events.push(inputDelta(tool, text)) + return { + tools: withTool(tools, key, tool), + tool, + events, + } +} export const isError = (result: AppendOutcome | LLMError): result is LLMError => result instanceof LLMError @@ -121,7 +133,8 @@ export const appendOrStart = ( providerExecuted: current?.providerExecuted, providerMetadata: current?.providerMetadata, } - if (current && delta.text.length === 0 && current.id === id && current.name === name) return { tools, tool: current } + if (current && delta.text.length === 0 && current.id === id && current.name === name) + return { tools, tool: current, events: [] } return appendTool(tools, key, tool, delta.text) } @@ -139,7 +152,7 @@ export const appendExisting = ( ): AppendOutcome | LLMError => { const current = tools[key] if (!current) return eventError(route, missingToolMessage) - if (text.length === 0) return { tools, tool: current } + if (text.length === 0) return { tools, tool: current, events: [] } return appendTool(tools, key, { ...current, input: `${current.input}${text}` }, text) } @@ -152,7 +165,13 @@ export const finish = (route: string, tools: State, key: Effect.gen(function* () { const tool = tools[key] if (!tool) return { tools } - return { tools: withoutTool(tools, key), event: yield* toolCall(route, tool) } + return { + tools: withoutTool(tools, key), + events: [ + LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }), + yield* toolCall(route, tool), + ], + } }) /** @@ -164,7 +183,13 @@ export const finishWithInput = (route: string, tools: State Effect.gen(function* () { const tool = tools[key] if (!tool) return { tools } - return { tools: withoutTool(tools, key), event: yield* toolCall(route, tool, input) } + return { + tools: withoutTool(tools, key), + events: [ + LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }), + yield* toolCall(route, tool, input), + ], + } }) /** @@ -179,7 +204,14 @@ export const finishAll = (route: string, tools: State) = ) return { tools: empty(), - events: yield* Effect.forEach(pending, (tool) => toolCall(route, tool)), + events: yield* Effect.forEach(pending, (tool) => + toolCall(route, tool).pipe( + Effect.map((call) => [ + LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }), + call, + ]), + ), + ).pipe(Effect.map((events) => events.flat())), } }) diff --git a/packages/llm/src/tool-runtime.ts b/packages/llm/src/tool-runtime.ts index c6e716d45..f46452582 100644 --- a/packages/llm/src/tool-runtime.ts +++ b/packages/llm/src/tool-runtime.ts @@ -154,8 +154,8 @@ const accumulate = (state: StepState, event: LLMEvent) => { ) return } - if (event.type === "request-finish") { - state.finishReason = event.reason + if (event.type === "step-finish" || event.type === "request-finish") { + state.finishReason = event.reason === "stop" && state.toolCalls.length > 0 ? "tool-calls" : event.reason } } diff --git a/packages/llm/test/provider/anthropic-messages.test.ts b/packages/llm/test/provider/anthropic-messages.test.ts index 0df3541d5..6417f73c2 100644 --- a/packages/llm/test/provider/anthropic-messages.test.ts +++ b/packages/llm/test/provider/anthropic-messages.test.ts @@ -146,24 +146,46 @@ describe("Anthropic Messages route", () => { tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }], }), ).pipe(Effect.provide(fixedResponse(body))) + const usage = new Usage({ + inputTokens: 5, + outputTokens: 1, + nonCachedInputTokens: 5, + cacheReadInputTokens: undefined, + cacheWriteInputTokens: undefined, + totalTokens: 6, + providerMetadata: { anthropic: { input_tokens: 5, output_tokens: 1 } }, + }) expect(response.toolCalls).toEqual([ - { type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" } }, + { + type: "tool-call", + id: "call_1", + name: "lookup", + input: { query: "weather" }, + providerExecuted: undefined, + providerMetadata: undefined, + }, ]) expect(response.events).toEqual([ + { type: "step-start", index: 0 }, + { type: "tool-input-start", id: "call_1", name: "lookup" }, { type: "tool-input-delta", id: "call_1", name: "lookup", text: '{"query"' }, { type: "tool-input-delta", id: "call_1", name: "lookup", text: ':"weather"}' }, - { type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" } }, + { type: "tool-input-end", id: "call_1", name: "lookup", providerMetadata: undefined }, + { + type: "tool-call", + id: "call_1", + name: "lookup", + input: { query: "weather" }, + providerExecuted: undefined, + providerMetadata: undefined, + }, + { type: "step-finish", index: 0, reason: "tool-calls", usage, providerMetadata: undefined }, { type: "request-finish", reason: "tool-calls", - usage: new Usage({ - inputTokens: 5, - outputTokens: 1, - nonCachedInputTokens: 5, - totalTokens: 6, - providerMetadata: { anthropic: { input_tokens: 5, output_tokens: 1 } }, - }), + providerMetadata: undefined, + usage, }, ]) }), diff --git a/packages/llm/test/provider/gemini.test.ts b/packages/llm/test/provider/gemini.test.ts index ea4eadc49..80c32c58b 100644 --- a/packages/llm/test/provider/gemini.test.ts +++ b/packages/llm/test/provider/gemini.test.ts @@ -204,30 +204,37 @@ describe("Gemini route", () => { reasoningTokens: 1, totalTokens: 7, }) + const usage = new Usage({ + inputTokens: 5, + outputTokens: 3, + nonCachedInputTokens: 4, + cacheReadInputTokens: 1, + reasoningTokens: 1, + totalTokens: 7, + providerMetadata: { + google: { + promptTokenCount: 5, + candidatesTokenCount: 2, + totalTokenCount: 7, + thoughtsTokenCount: 1, + cachedContentTokenCount: 1, + }, + }, + }) expect(response.events).toEqual([ + { type: "step-start", index: 0 }, + { type: "reasoning-start", id: "reasoning-0" }, { type: "reasoning-delta", id: "reasoning-0", text: "thinking" }, + { type: "text-start", id: "text-0" }, { type: "text-delta", id: "text-0", text: "Hello" }, { type: "text-delta", id: "text-0", text: "!" }, + { type: "reasoning-end", id: "reasoning-0" }, + { type: "text-end", id: "text-0" }, + { type: "step-finish", index: 0, reason: "stop", usage, providerMetadata: undefined }, { type: "request-finish", reason: "stop", - usage: new Usage({ - inputTokens: 5, - outputTokens: 3, - nonCachedInputTokens: 4, - cacheReadInputTokens: 1, - reasoningTokens: 1, - totalTokens: 7, - providerMetadata: { - google: { - promptTokenCount: 5, - candidatesTokenCount: 2, - totalTokenCount: 7, - thoughtsTokenCount: 1, - cachedContentTokenCount: 1, - }, - }, - }), + usage, }, ]) }), @@ -252,22 +259,41 @@ describe("Gemini route", () => { tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }], }), ).pipe(Effect.provide(fixedResponse(body))) + const usage = new Usage({ + inputTokens: 5, + outputTokens: 1, + nonCachedInputTokens: 5, + cacheReadInputTokens: undefined, + reasoningTokens: undefined, + totalTokens: 6, + providerMetadata: { google: { promptTokenCount: 5, candidatesTokenCount: 1 } }, + }) expect(response.toolCalls).toEqual([ - { type: "tool-call", id: "tool_0", name: "lookup", input: { query: "weather" } }, + { + type: "tool-call", + id: "tool_0", + name: "lookup", + input: { query: "weather" }, + providerExecuted: undefined, + providerMetadata: undefined, + }, ]) expect(response.events).toEqual([ - { type: "tool-call", id: "tool_0", name: "lookup", input: { query: "weather" } }, + { type: "step-start", index: 0 }, + { + type: "tool-call", + id: "tool_0", + name: "lookup", + input: { query: "weather" }, + providerExecuted: undefined, + providerMetadata: undefined, + }, + { type: "step-finish", index: 0, reason: "tool-calls", usage, providerMetadata: undefined }, { type: "request-finish", reason: "tool-calls", - usage: new Usage({ - inputTokens: 5, - outputTokens: 1, - nonCachedInputTokens: 5, - totalTokens: 6, - providerMetadata: { google: { promptTokenCount: 5, candidatesTokenCount: 1 } }, - }), + usage, }, ]) }), @@ -318,8 +344,10 @@ describe("Gemini route", () => { ), ) - expect(length.events).toEqual([{ type: "request-finish", reason: "length" }]) - expect(filtered.events).toEqual([{ type: "request-finish", reason: "content-filter" }]) + expect(length.events.map((event) => event.type)).toEqual(["step-start", "step-finish", "request-finish"]) + expect(length.events.at(-1)).toMatchObject({ type: "request-finish", reason: "length" }) + expect(filtered.events.map((event) => event.type)).toEqual(["step-start", "step-finish", "request-finish"]) + expect(filtered.events.at(-1)).toMatchObject({ type: "request-finish", reason: "content-filter" }) }), ) diff --git a/packages/llm/test/provider/openai-chat.test.ts b/packages/llm/test/provider/openai-chat.test.ts index 9c8142263..115c58849 100644 --- a/packages/llm/test/provider/openai-chat.test.ts +++ b/packages/llm/test/provider/openai-chat.test.ts @@ -222,31 +222,36 @@ describe("OpenAI Chat route", () => { }), ) const response = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body))) + const usage = new Usage({ + inputTokens: 5, + outputTokens: 2, + nonCachedInputTokens: 4, + cacheReadInputTokens: 1, + reasoningTokens: 0, + totalTokens: 7, + providerMetadata: { + openai: { + prompt_tokens: 5, + completion_tokens: 2, + total_tokens: 7, + prompt_tokens_details: { cached_tokens: 1 }, + completion_tokens_details: { reasoning_tokens: 0 }, + }, + }, + }) expect(response.text).toBe("Hello!") expect(response.events).toEqual([ + { type: "step-start", index: 0 }, + { type: "text-start", id: "text-0" }, { type: "text-delta", id: "text-0", text: "Hello" }, { type: "text-delta", id: "text-0", text: "!" }, + { type: "text-end", id: "text-0" }, + { type: "step-finish", index: 0, reason: "stop", usage, providerMetadata: undefined }, { type: "request-finish", reason: "stop", - usage: new Usage({ - inputTokens: 5, - outputTokens: 2, - nonCachedInputTokens: 4, - cacheReadInputTokens: 1, - reasoningTokens: 0, - totalTokens: 7, - providerMetadata: { - openai: { - prompt_tokens: 5, - completion_tokens: 2, - total_tokens: 7, - prompt_tokens_details: { cached_tokens: 1 }, - completion_tokens_details: { reasoning_tokens: 0 }, - }, - }, - }), + usage, }, ]) }), @@ -269,9 +274,20 @@ describe("OpenAI Chat route", () => { ).pipe(Effect.provide(fixedResponse(body))) expect(response.events).toEqual([ + { type: "step-start", index: 0 }, + { type: "tool-input-start", id: "call_1", name: "lookup", providerMetadata: undefined }, { type: "tool-input-delta", id: "call_1", name: "lookup", text: '{"query"' }, { type: "tool-input-delta", id: "call_1", name: "lookup", text: ':"weather"}' }, - { type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" } }, + { type: "tool-input-end", id: "call_1", name: "lookup", providerMetadata: undefined }, + { + type: "tool-call", + id: "call_1", + name: "lookup", + input: { query: "weather" }, + providerExecuted: undefined, + providerMetadata: undefined, + }, + { type: "step-finish", index: 0, reason: "tool-calls", usage: undefined, providerMetadata: undefined }, { type: "request-finish", reason: "tool-calls", usage: undefined }, ]) }), @@ -293,6 +309,8 @@ describe("OpenAI Chat route", () => { ).pipe(Effect.provide(fixedResponse(body))) expect(response.events).toEqual([ + { type: "step-start", index: 0 }, + { type: "tool-input-start", id: "call_1", name: "lookup", providerMetadata: undefined }, { type: "tool-input-delta", id: "call_1", name: "lookup", text: '{"query"' }, { type: "tool-input-delta", id: "call_1", name: "lookup", text: ':"weather"}' }, ]) @@ -352,7 +370,7 @@ describe("OpenAI Chat route", () => { const events = Array.from( yield* LLMClient.stream(request).pipe(Stream.take(1), Stream.runCollect, Effect.provide(fixedResponse(body))), ) - expect(events.map((event) => event.type)).toEqual(["text-delta"]) + expect(events.map((event) => event.type)).toEqual(["step-start"]) }), ) }) diff --git a/packages/llm/test/provider/openai-responses.test.ts b/packages/llm/test/provider/openai-responses.test.ts index da9dbd82c..8b4469f4e 100644 --- a/packages/llm/test/provider/openai-responses.test.ts +++ b/packages/llm/test/provider/openai-responses.test.ts @@ -333,32 +333,43 @@ describe("OpenAI Responses route", () => { }, ) const response = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body))) + const usage = new Usage({ + inputTokens: 5, + outputTokens: 2, + nonCachedInputTokens: 4, + cacheReadInputTokens: 1, + reasoningTokens: 0, + totalTokens: 7, + providerMetadata: { + openai: { + input_tokens: 5, + output_tokens: 2, + total_tokens: 7, + input_tokens_details: { cached_tokens: 1 }, + output_tokens_details: { reasoning_tokens: 0 }, + }, + }, + }) expect(response.text).toBe("Hello!") expect(response.events).toEqual([ + { type: "step-start", index: 0 }, + { type: "text-start", id: "msg_1" }, { type: "text-delta", id: "msg_1", text: "Hello" }, { type: "text-delta", id: "msg_1", text: "!" }, + { type: "text-end", id: "msg_1" }, + { + type: "step-finish", + index: 0, + reason: "stop", + providerMetadata: { openai: { responseId: "resp_1", serviceTier: "default" } }, + usage, + }, { type: "request-finish", reason: "stop", providerMetadata: { openai: { responseId: "resp_1", serviceTier: "default" } }, - usage: new Usage({ - inputTokens: 5, - outputTokens: 2, - nonCachedInputTokens: 4, - cacheReadInputTokens: 1, - reasoningTokens: 0, - totalTokens: 7, - providerMetadata: { - openai: { - input_tokens: 5, - output_tokens: 2, - total_tokens: 7, - input_tokens_details: { cached_tokens: 1 }, - output_tokens_details: { reasoning_tokens: 0 }, - }, - }, - }), + usage, }, ]) }), @@ -390,8 +401,24 @@ describe("OpenAI Responses route", () => { tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }], }), ).pipe(Effect.provide(fixedResponse(body))) + const usage = new Usage({ + inputTokens: 5, + outputTokens: 1, + nonCachedInputTokens: 5, + cacheReadInputTokens: undefined, + reasoningTokens: undefined, + totalTokens: 6, + providerMetadata: { openai: { input_tokens: 5, output_tokens: 1 } }, + }) expect(response.events).toEqual([ + { type: "step-start", index: 0 }, + { + type: "tool-input-start", + id: "call_1", + name: "lookup", + providerMetadata: { openai: { itemId: "item_1" } }, + }, { type: "tool-input-delta", id: "call_1", @@ -404,23 +431,26 @@ describe("OpenAI Responses route", () => { name: "lookup", text: ':"weather"}', }, + { + type: "tool-input-end", + id: "call_1", + name: "lookup", + providerMetadata: { openai: { itemId: "item_1" } }, + }, { type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" }, + providerExecuted: undefined, providerMetadata: { openai: { itemId: "item_1" } }, }, + { type: "step-finish", index: 0, reason: "tool-calls", usage, providerMetadata: undefined }, { type: "request-finish", reason: "tool-calls", - usage: new Usage({ - inputTokens: 5, - outputTokens: 1, - nonCachedInputTokens: 5, - totalTokens: 6, - providerMetadata: { openai: { input_tokens: 5, output_tokens: 1 } }, - }), + providerMetadata: undefined, + usage, }, ]) }), diff --git a/packages/llm/test/tool-runtime.test.ts b/packages/llm/test/tool-runtime.test.ts index 8f4221784..040a11fb6 100644 --- a/packages/llm/test/tool-runtime.test.ts +++ b/packages/llm/test/tool-runtime.test.ts @@ -313,7 +313,14 @@ describe("LLMClient tools", () => { ), ) - expect(events.map((event) => event.type)).toEqual(["text-delta", "request-finish"]) + expect(events.map((event) => event.type)).toEqual([ + "step-start", + "text-start", + "text-delta", + "text-end", + "step-finish", + "request-finish", + ]) expect(LLMResponse.text({ events })).toBe("Done.") }), ) diff --git a/packages/llm/test/tool-stream.test.ts b/packages/llm/test/tool-stream.test.ts index 04a0035c9..b005d2666 100644 --- a/packages/llm/test/tool-stream.test.ts +++ b/packages/llm/test/tool-stream.test.ts @@ -21,11 +21,17 @@ describe("ToolStream", () => { if (ToolStream.isError(second)) return yield* second const finished = yield* ToolStream.finish(ADAPTER, second.tools, 0) - expect(first.event).toEqual({ type: "tool-input-delta", id: "call_1", name: "lookup", text: '{"query"' }) - expect(second.event).toEqual({ type: "tool-input-delta", id: "call_1", name: "lookup", text: ':"weather"}' }) + expect(first.events).toEqual([ + { type: "tool-input-start", id: "call_1", name: "lookup" }, + { type: "tool-input-delta", id: "call_1", name: "lookup", text: '{"query"' }, + ]) + expect(second.events).toEqual([{ type: "tool-input-delta", id: "call_1", name: "lookup", text: ':"weather"}' }]) expect(finished).toEqual({ tools: {}, - event: { type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" } }, + events: [ + { type: "tool-input-end", id: "call_1", name: "lookup" }, + { type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" } }, + ], }) }), ) @@ -50,7 +56,10 @@ describe("ToolStream", () => { expect(finished).toEqual({ tools: {}, - event: { type: "tool-call", id: "call_1", name: "lookup", input: { query: "final" } }, + events: [ + { type: "tool-input-end", id: "call_1", name: "lookup" }, + { type: "tool-call", id: "call_1", name: "lookup", input: { query: "final" } }, + ], }) }), ) @@ -73,7 +82,9 @@ describe("ToolStream", () => { expect(finished).toEqual({ tools: {}, events: [ + { type: "tool-input-end", id: "call_1", name: "lookup" }, { type: "tool-call", id: "call_1", name: "lookup", input: {} }, + { type: "tool-input-end", id: "call_2", name: "web_search" }, { type: "tool-call", id: "call_2", From 74aa735e6ac84078b4dd04bb1b93a611b8946885 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 21:35:28 -0400 Subject: [PATCH 067/378] fix(tui): guard prompt submit against concurrent invocation (#26972) --- .../cli/cmd/tui/component/prompt/index.tsx | 17 ++++ .../test/cli/tui/prompt-submit-race.test.ts | 98 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 packages/opencode/test/cli/tui/prompt-submit-race.test.ts 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 f3217fcba..3bbfc261b 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -989,7 +989,24 @@ export function Prompt(props: PromptProps) { } }) + let submitting = false async function submit() { + // Prevent overlapping invocations (e.g. a double-pressed Enter, or the + // input's native onSubmit racing another dispatch). Without this guard, + // a second call slips past the empty-input check before the first call + // clears `store.prompt.input`, then awaits its own `session.create` and + // ultimately reads the now-empty store — sending a phantom empty prompt + // to a freshly created session. + if (submitting) return false + submitting = true + try { + return await submitInner() + } finally { + submitting = false + } + } + + async function submitInner() { setWarpNotice(undefined) // IME: double-defer may fire before onContentChange flushes the last diff --git a/packages/opencode/test/cli/tui/prompt-submit-race.test.ts b/packages/opencode/test/cli/tui/prompt-submit-race.test.ts new file mode 100644 index 000000000..df659a01d --- /dev/null +++ b/packages/opencode/test/cli/tui/prompt-submit-race.test.ts @@ -0,0 +1,98 @@ +import { describe, expect, test } from "bun:test" + +// Regression test for the prompt submit race in +// packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx (`submit`). +// +// Before the fix, two concurrent `submit()` calls (e.g. a double-pressed +// Enter, or the input's native onSubmit racing another dispatch) each +// passed the `if (!store.prompt.input) return false` guard, each +// `await sdk.client.session.create(...)`, and each only captured +// `inputText = store.prompt.input` AFTER that await. The first invocation +// finished, sent the prompt, and cleared the store; the second invocation, +// now past its await, read the cleared store and sent an empty prompt to a +// second freshly-created session - leaving an orphaned session with the +// user's actual text and a phantom session visible to the user containing +// only an assistant reply. +// +// `submitMirror` below has the exact shape of the production `submit()` +// after the fix: an in-flight `submitting` guard wraps the original body. +// Two concurrent invocations must result in exactly one submission carrying +// the user's text, with no empty-text submission. + +type Store = { input: string } + +type SubmitResult = { sessionID: string; text: string } + +type Harness = { + store: Store + submissions: SubmitResult[] + createSession(): Promise + sendPrompt(sessionID: string, text: string): Promise +} + +function createHarness(opts: { sessionCreateDelayMs: number }): Harness { + let sessionCounter = 0 + const submissions: SubmitResult[] = [] + + return { + store: { input: "" }, + submissions, + async createSession() { + sessionCounter += 1 + const id = `ses_${sessionCounter}` + await Bun.sleep(opts.sessionCreateDelayMs) + return id + }, + async sendPrompt(sessionID, text) { + submissions.push({ sessionID, text }) + }, + } +} + +function createSubmit() { + let submitting = false + return async function submit(h: Harness) { + if (submitting) return false + submitting = true + try { + if (!h.store.input) return false + const sessionID = await h.createSession() + const inputText = h.store.input + await h.sendPrompt(sessionID, inputText) + h.store.input = "" + return true + } finally { + submitting = false + } + } +} + +describe("Prompt.submit race", () => { + test("concurrent submits must not lose the user's text", async () => { + const submit = createSubmit() + const h = createHarness({ sessionCreateDelayMs: 5 }) + h.store.input = "Hello there." + + // Two invocations back-to-back, mimicking a double-Enter. + await Promise.all([submit(h), submit(h)]) + + // Every submission that did make it through must carry the actual user + // text, and no submission may have an empty text payload. + expect(h.submissions.every((s) => s.text === "Hello there.")).toBe(true) + expect(h.submissions.some((s) => s.text === "")).toBe(false) + }) + + test("a sequential second submit after clear is a no-op, not a phantom session", async () => { + const submit = createSubmit() + const h = createHarness({ sessionCreateDelayMs: 1 }) + h.store.input = "Hello there." + + await submit(h) + // After the first submission completes, the store is cleared; a second + // Enter on an empty input must not create a phantom session. + await submit(h) + + expect(h.submissions).toHaveLength(1) + expect(h.submissions[0].text).toBe("Hello there.") + }) +}) From 9e8274d2dacf0632d91e4c41f465e125e0d74da9 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 21:40:44 -0400 Subject: [PATCH 068/378] Remove internal Zod schemas (#26974) --- packages/opencode/src/file/watcher.ts | 1 - packages/opencode/src/installation/index.ts | 15 +++++---------- packages/opencode/src/patch/index.ts | 9 ++++----- packages/opencode/src/provider/transform.ts | 10 ++++++---- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 146d7b4d0..ecbf76424 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -4,7 +4,6 @@ import { createWrapper } from "@parcel/watcher/wrapper" import type ParcelWatcher from "@parcel/watcher" import { readdir } from "fs/promises" import path from "path" -import z from "zod" import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index be3bc4769..e8c434276 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -4,7 +4,6 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { withTransientReadRetry } from "@/util/effect-http-client" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import path from "path" -import z from "zod" import { BusEvent } from "@/bus/bus-event" import { Flag } from "@opencode-ai/core/flag/flag" import * as Log from "@opencode-ai/core/util/log" @@ -45,15 +44,11 @@ export function getReleaseType(current: string, latest: string): ReleaseType { return "patch" } -export const Info = z - .object({ - version: z.string(), - latest: z.string(), - }) - .meta({ - ref: "InstallationInfo", - }) -export type Info = z.infer +export const Info = Schema.Struct({ + version: Schema.String, + latest: Schema.String, +}).annotate({ identifier: "InstallationInfo" }) +export type Info = Schema.Schema.Type export const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}` diff --git a/packages/opencode/src/patch/index.ts b/packages/opencode/src/patch/index.ts index fd5fff562..3dfa6f2d0 100644 --- a/packages/opencode/src/patch/index.ts +++ b/packages/opencode/src/patch/index.ts @@ -1,4 +1,4 @@ -import z from "zod" +import { Schema } from "effect" import * as path from "path" import * as fs from "fs/promises" import { readFileSync } from "fs" @@ -7,12 +7,11 @@ import * as Bom from "../util/bom" const log = Log.create({ service: "patch" }) -// Schema definitions -export const PatchSchema = z.object({ - patchText: z.string().describe("The full patch text that describes all changes to be made"), +export const PatchSchema = Schema.Struct({ + patchText: Schema.String.annotate({ description: "The full patch text that describes all changes to be made" }), }) -export type PatchParams = z.infer +export type PatchParams = Schema.Schema.Type // Core types matching the Rust implementation export interface ApplyPatchArgs { diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index bd778dacc..72ec881e7 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1,7 +1,6 @@ import type { ModelMessage, ToolResultPart } from "ai" import { mergeDeep, unique } from "remeda" import type { JSONSchema7 } from "@ai-sdk/provider" -import type { JSONSchema } from "zod/v4/core" import type * as Provider from "./provider" import type * as ModelsDev from "./models" import { iife } from "@/util/iife" @@ -1281,7 +1280,7 @@ export function maxOutputTokens(model: Provider.Model): number { return Math.min(model.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX } -export function schema(model: Provider.Model, schema: JSONSchema.BaseSchema | JSONSchema7): JSONSchema7 { +export function schema(model: Provider.Model, schema: JSONSchema7): JSONSchema7 { /* if (["openai", "azure"].includes(providerID)) { if (schema.type === "object" && schema.properties) { @@ -1312,7 +1311,10 @@ export function schema(model: Provider.Model, schema: JSONSchema.BaseSchema | JS return result } - schema = sanitizeMoonshot(schema) as JSONSchema.BaseSchema | JSONSchema7 + const sanitized = sanitizeMoonshot(schema) + if (typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) { + schema = sanitized + } } // Convert integer enums to string enums for Google/Gemini @@ -1394,7 +1396,7 @@ export function schema(model: Provider.Model, schema: JSONSchema.BaseSchema | JS schema = sanitizeGemini(schema) } - return schema as JSONSchema7 + return schema } export * as ProviderTransform from "./transform" From 100763034792ccf0386adfa356e097d5f825f5c0 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 21:41:56 -0400 Subject: [PATCH 069/378] Migrate runtime validators to Effect Schema (#26975) --- .../src/cli/cmd/tui/context/editor-zed.ts | 47 ++--- .../src/cli/cmd/tui/context/editor.ts | 161 +++++++++--------- packages/opencode/src/mcp/auth.ts | 52 +++--- .../src/plugin/github-copilot/models.ts | 77 ++++----- 4 files changed, 176 insertions(+), 161 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts b/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts index 6805f0b66..611db406b 100644 --- a/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts +++ b/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts @@ -1,33 +1,36 @@ import { Database } from "bun:sqlite" import os from "node:os" import path from "node:path" -import z from "zod" +import { Option, Schema } from "effect" import { Filesystem } from "@/util/filesystem" import type { EditorSelection } from "./editor" -const ZedEditorRowSchema = z.object({ - item_kind: z.string(), - editor_id: z.number().nullable(), - workspace_id: z.number(), - workspace_paths: z.string().nullable(), - timestamp: z.string(), - buffer_path: z.string().nullable(), +const ZedEditorRowSchema = Schema.Struct({ + item_kind: Schema.String, + editor_id: Schema.NullOr(Schema.Number), + workspace_id: Schema.Number, + workspace_paths: Schema.NullOr(Schema.String), + timestamp: Schema.String, + buffer_path: Schema.NullOr(Schema.String), }) -const ZedSelectionRowSchema = z.object({ - selection_start: z.number().nullable(), - selection_end: z.number().nullable(), +const ZedSelectionRowSchema = Schema.Struct({ + selection_start: Schema.NullOr(Schema.Number), + selection_end: Schema.NullOr(Schema.Number), }) -const ZedEditorContentsSchema = z.object({ - contents: z.string().nullable(), +const ZedEditorContentsSchema = Schema.Struct({ + contents: Schema.NullOr(Schema.String), }) +const decodeZedEditorRow = Schema.decodeUnknownOption(ZedEditorRowSchema) +const decodeZedSelectionRow = Schema.decodeUnknownOption(ZedSelectionRowSchema) +const decodeZedEditorContents = Schema.decodeUnknownOption(ZedEditorContentsSchema) + const utf8 = new TextEncoder() -type ZedEditorRow = z.infer +type ZedEditorRow = Schema.Schema.Type type ZedActiveEditorRow = ZedEditorRow & { item_kind: "Editor"; editor_id: number } -type ZedSelectionRow = z.infer export type ZedSelectionResult = | { type: "selection"; selection: EditorSelection } @@ -107,8 +110,8 @@ function queryZedActiveEditor(dbPath: string, cwd: string) { .all() const rows = raw.flatMap((row) => { - const parsed = ZedEditorRowSchema.safeParse(row) - return parsed.success ? [parsed.data] : [] + const parsed = decodeZedEditorRow(row) + return Option.isSome(parsed) ? [parsed.value] : [] }) if (raw.length > 0 && rows.length === 0) return { type: "unavailable" as const } @@ -143,8 +146,8 @@ function queryZedEditorSelections(dbPath: string, row: ZedActiveEditorRow) { .all({ $editorID: row.editor_id, $workspaceID: row.workspace_id }) const selections = raw.flatMap((selection) => { - const parsed = ZedSelectionRowSchema.safeParse(selection) - return parsed.success ? [parsed.data] : [] + const parsed = decodeZedSelectionRow(selection) + return Option.isSome(parsed) ? [parsed.value] : [] }) if (raw.length > 0 && selections.length === 0) return { type: "unavailable" as const } @@ -160,7 +163,7 @@ function queryZedEditorContents(dbPath: string, row: ZedActiveEditorRow) { let db: Database | undefined try { db = new Database(dbPath, { readonly: true }) - const parsed = ZedEditorContentsSchema.safeParse( + const parsed = decodeZedEditorContents( db .query( `select contents @@ -169,8 +172,8 @@ function queryZedEditorContents(dbPath: string, row: ZedActiveEditorRow) { ) .get({ $editorID: row.editor_id, $workspaceID: row.workspace_id }), ) - if (!parsed.success) return { type: "unavailable" as const } - return { type: "contents" as const, contents: parsed.data.contents } + if (Option.isNone(parsed)) return { type: "unavailable" as const } + return { type: "contents" as const, contents: parsed.value.contents } } catch { return { type: "unavailable" as const } } finally { diff --git a/packages/opencode/src/cli/cmd/tui/context/editor.ts b/packages/opencode/src/cli/cmd/tui/context/editor.ts index 6d9e04cf8..ea7fd5810 100644 --- a/packages/opencode/src/cli/cmd/tui/context/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/context/editor.ts @@ -3,92 +3,102 @@ import os from "node:os" import path from "node:path" import { onCleanup, onMount } from "solid-js" import { createStore } from "solid-js/store" -import z from "zod" +import { Option, Schema, SchemaGetter } from "effect" import { isRecord } from "@/util/record" import { createSimpleContext } from "./helper" import { resolveZedDbPath, resolveZedSelection } from "./editor-zed" const MCP_PROTOCOL_VERSION = "2025-11-25" -const JsonRpcMessageSchema = z.object({ - id: z.union([z.number(), z.string(), z.null()]).optional(), - method: z.string().optional(), - params: z.unknown().optional(), - result: z.unknown().optional(), - error: z - .object({ - code: z.number().optional(), - message: z.string().optional(), - }) - .optional(), +const JsonRpcMessageSchema = Schema.Struct({ + id: Schema.optional(Schema.Union([Schema.Number, Schema.String, Schema.Null])), + method: Schema.optional(Schema.String), + params: Schema.optional(Schema.Unknown), + result: Schema.optional(Schema.Unknown), + error: Schema.optional( + Schema.Struct({ + code: Schema.optional(Schema.Number), + message: Schema.optional(Schema.String), + }), + ), }) -const PositionSchema = z.object({ - line: z.number(), - character: z.number(), +const PositionSchema = Schema.Struct({ + line: Schema.Number, + character: Schema.Number, }) -const EditorSelectionRangeSchema = z.object({ - text: z.string(), - selection: z.object({ +const EditorSelectionRangeSchema = Schema.Struct({ + text: Schema.String, + selection: Schema.Struct({ start: PositionSchema, end: PositionSchema, }), }) -const EditorSelectionSchema = z - .union([ - z.object({ - filePath: z.string(), - source: z.enum(["websocket", "zed"]).optional(), - ranges: z.array(EditorSelectionRangeSchema).min(1), - }), - z.object({ - text: z.string(), - filePath: z.string(), - source: z.enum(["websocket", "zed"]).optional(), - selection: z.object({ - start: PositionSchema, - end: PositionSchema, - }), +const EditorSelectionRangesSchema = Schema.Struct({ + filePath: Schema.String, + source: Schema.optional(Schema.Literals(["websocket", "zed"])), + ranges: Schema.mutable(Schema.Array(EditorSelectionRangeSchema).check(Schema.isMinLength(1))), +}) + +const EditorSelectionSchema = Schema.Union([ + EditorSelectionRangesSchema, + Schema.Struct({ + text: Schema.String, + filePath: Schema.String, + source: Schema.optional(Schema.Literals(["websocket", "zed"])), + selection: Schema.Struct({ + start: PositionSchema, + end: PositionSchema, }), - ]) - .transform((value) => - "ranges" in value - ? value - : { - filePath: value.filePath, - source: value.source, - ranges: [ - { - text: value.text, - selection: value.selection, - }, - ], - }, - ) - -const EditorMentionSchema = z.object({ - filePath: z.string(), - lineStart: z.number(), - lineEnd: z.number(), + }), +]).pipe( + Schema.decodeTo(EditorSelectionRangesSchema, { + decode: SchemaGetter.transform((value) => + "ranges" in value + ? value + : { + filePath: value.filePath, + source: value.source, + ranges: [ + { + text: value.text, + selection: value.selection, + }, + ], + }, + ), + encode: SchemaGetter.passthrough({ strict: false }), + }), +) + +const EditorMentionSchema = Schema.Struct({ + filePath: Schema.String, + lineStart: Schema.Number, + lineEnd: Schema.Number, }) -const EditorServerInfoSchema = z.object({ - protocolVersion: z.string().optional(), - serverInfo: z - .object({ - name: z.string().optional(), - version: z.string().optional(), - }) - .optional(), +const EditorServerInfoSchema = Schema.Struct({ + protocolVersion: Schema.optional(Schema.String), + serverInfo: Schema.optional( + Schema.Struct({ + name: Schema.optional(Schema.String), + version: Schema.optional(Schema.String), + }), + ), }) -type JsonRpcMessage = z.infer -export type EditorSelection = z.infer -export type EditorMention = z.infer +const decodeJsonRpcMessage = Schema.decodeUnknownOption(JsonRpcMessageSchema) +const decodeEditorSelection = Schema.decodeUnknownOption(EditorSelectionSchema) +const decodeEditorMention = Schema.decodeUnknownOption(EditorMentionSchema) +const decodeEditorServerInfo = Schema.decodeUnknownOption(EditorServerInfoSchema) + +type JsonRpcMessage = Schema.Schema.Type +export type EditorSelection = Schema.Schema.Type +export type EditorMention = Schema.Schema.Type export type EditorLabelState = "pending" | "sent" | "none" -type EditorServerInfo = z.infer +type EditorServerInfo = Schema.Schema.Type type EditorConnection = { url: string @@ -214,16 +224,15 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create const message = parseMessage(event.data) if (!message) return - const selection = - message.method === "selection_changed" ? EditorSelectionSchema.safeParse(message.params) : undefined - if (selection?.success) { - setSelection({ ...selection.data, source: "websocket" }) + const selection = message.method === "selection_changed" ? decodeEditorSelection(message.params) : Option.none() + if (Option.isSome(selection)) { + setSelection({ ...selection.value, source: "websocket" }) return } - const mention = message.method === "at_mentioned" ? EditorMentionSchema.safeParse(message.params) : undefined - if (mention?.success) { - mentionListeners.forEach((listener) => listener(mention.data)) + const mention = message.method === "at_mentioned" ? decodeEditorMention(message.params) : Option.none() + if (Option.isSome(mention)) { + mentionListeners.forEach((listener) => listener(mention.value)) return } @@ -235,9 +244,9 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create pending.delete(message.id) if (message.error) return - const initialize = method === "initialize" ? EditorServerInfoSchema.safeParse(message.result) : undefined - if (initialize?.success) { - setStore("server", initialize.data) + const initialize = method === "initialize" ? decodeEditorServerInfo(message.result) : Option.none() + if (Option.isSome(initialize)) { + setStore("server", initialize.value) send({ method: "notifications/initialized" }) return } @@ -447,7 +456,7 @@ function parseMessage(value: unknown) { if (typeof value !== "string") return try { - return JsonRpcMessageSchema.parse(JSON.parse(value)) + return Option.getOrUndefined(decodeJsonRpcMessage(JSON.parse(value))) } catch { return } diff --git a/packages/opencode/src/mcp/auth.ts b/packages/opencode/src/mcp/auth.ts index b07d59870..be19be0af 100644 --- a/packages/opencode/src/mcp/auth.ts +++ b/packages/opencode/src/mcp/auth.ts @@ -1,33 +1,35 @@ import path from "path" -import z from "zod" import { Global } from "@opencode-ai/core/global" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Option, Schema } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" -export const Tokens = z.object({ - accessToken: z.string(), - refreshToken: z.string().optional(), - expiresAt: z.number().optional(), - scope: z.string().optional(), +export const Tokens = Schema.Struct({ + accessToken: Schema.mutableKey(Schema.String), + refreshToken: Schema.mutableKey(Schema.optional(Schema.String)), + expiresAt: Schema.mutableKey(Schema.optional(Schema.Number)), + scope: Schema.mutableKey(Schema.optional(Schema.String)), }) -export type Tokens = z.infer +export type Tokens = Schema.Schema.Type -export const ClientInfo = z.object({ - clientId: z.string(), - clientSecret: z.string().optional(), - clientIdIssuedAt: z.number().optional(), - clientSecretExpiresAt: z.number().optional(), +export const ClientInfo = Schema.Struct({ + clientId: Schema.mutableKey(Schema.String), + clientSecret: Schema.mutableKey(Schema.optional(Schema.String)), + clientIdIssuedAt: Schema.mutableKey(Schema.optional(Schema.Number)), + clientSecretExpiresAt: Schema.mutableKey(Schema.optional(Schema.Number)), }) -export type ClientInfo = z.infer - -export const Entry = z.object({ - tokens: Tokens.optional(), - clientInfo: ClientInfo.optional(), - codeVerifier: z.string().optional(), - oauthState: z.string().optional(), - serverUrl: z.string().optional(), +export type ClientInfo = Schema.Schema.Type + +export const Entry = Schema.Struct({ + tokens: Schema.mutableKey(Schema.optional(Tokens)), + clientInfo: Schema.mutableKey(Schema.optional(ClientInfo)), + codeVerifier: Schema.mutableKey(Schema.optional(Schema.String)), + oauthState: Schema.mutableKey(Schema.optional(Schema.String)), + serverUrl: Schema.mutableKey(Schema.optional(Schema.String)), }) -export type Entry = z.infer +export type Entry = Schema.Schema.Type + +const decodeAuthData = Schema.decodeUnknownOption(Schema.Record(Schema.String, Entry)) +type AuthData = Record const filepath = path.join(Global.Path.data, "mcp-auth.json") @@ -56,8 +58,8 @@ export const layer = Layer.effect( const all = Effect.fn("McpAuth.all")(function* () { return yield* fs.readJson(filepath).pipe( - Effect.map((data) => data as Record), - Effect.catch(() => Effect.succeed({} as Record)), + Effect.map((data): AuthData => Option.getOrElse(decodeAuthData(data), () => ({}) as AuthData) as AuthData), + Effect.catch(() => Effect.succeed({} as AuthData)), ) }) @@ -93,7 +95,7 @@ export const layer = Layer.effect( yield* set(mcpName, entry, serverUrl) }) - const clearField = (field: K, spanName: string) => + const clearField = (field: keyof Entry, spanName: string) => Effect.fn(`McpAuth.${spanName}`)(function* (mcpName: string) { const entry = yield* get(mcpName) if (entry) { diff --git a/packages/opencode/src/plugin/github-copilot/models.ts b/packages/opencode/src/plugin/github-copilot/models.ts index 8fa8dee76..a488be4a4 100644 --- a/packages/opencode/src/plugin/github-copilot/models.ts +++ b/packages/opencode/src/plugin/github-copilot/models.ts @@ -1,50 +1,51 @@ -import { z } from "zod" import type { Model } from "@opencode-ai/sdk/v2" +import { Schema } from "effect" -export const schema = z.object({ - data: z.array( - z.object({ - model_picker_enabled: z.boolean(), - id: z.string(), - name: z.string(), +export const schema = Schema.Struct({ + data: Schema.Array( + Schema.Struct({ + model_picker_enabled: Schema.Boolean, + id: Schema.String, + name: Schema.String, // every version looks like: `{model.id}-YYYY-MM-DD` - version: z.string(), - supported_endpoints: z.array(z.string()).optional(), - policy: z - .object({ - state: z.string().optional(), - }) - .optional(), - capabilities: z.object({ - family: z.string(), - limits: z.object({ - max_context_window_tokens: z.number(), - max_output_tokens: z.number(), - max_prompt_tokens: z.number(), - vision: z - .object({ - max_prompt_image_size: z.number(), - max_prompt_images: z.number(), - supported_media_types: z.array(z.string()), - }) - .optional(), + version: Schema.String, + supported_endpoints: Schema.optional(Schema.Array(Schema.String)), + policy: Schema.optional( + Schema.Struct({ + state: Schema.optional(Schema.String), }), - supports: z.object({ - adaptive_thinking: z.boolean().optional(), - max_thinking_budget: z.number().optional(), - min_thinking_budget: z.number().optional(), - reasoning_effort: z.array(z.string()).optional(), - streaming: z.boolean(), - structured_outputs: z.boolean().optional(), - tool_calls: z.boolean(), - vision: z.boolean().optional(), + ), + capabilities: Schema.Struct({ + family: Schema.String, + limits: Schema.Struct({ + max_context_window_tokens: Schema.Number, + max_output_tokens: Schema.Number, + max_prompt_tokens: Schema.Number, + vision: Schema.optional( + Schema.Struct({ + max_prompt_image_size: Schema.Number, + max_prompt_images: Schema.Number, + supported_media_types: Schema.Array(Schema.String), + }), + ), + }), + supports: Schema.Struct({ + adaptive_thinking: Schema.optional(Schema.Boolean), + max_thinking_budget: Schema.optional(Schema.Number), + min_thinking_budget: Schema.optional(Schema.Number), + reasoning_effort: Schema.optional(Schema.Array(Schema.String)), + streaming: Schema.Boolean, + structured_outputs: Schema.optional(Schema.Boolean), + tool_calls: Schema.Boolean, + vision: Schema.optional(Schema.Boolean), }), }), }), ), }) -type Item = z.infer["data"][number] +type Item = Schema.Schema.Type["data"][number] +const decodeModels = Schema.decodeUnknownSync(schema) function build(key: string, remote: Item, url: string, prev?: Model): Model { const reasoning = @@ -165,7 +166,7 @@ export async function get( if (!res.ok) { throw new Error(`Failed to fetch models: ${res.status}`) } - return schema.parse(await res.json()) + return decodeModels(await res.json()) }) const result = { ...existing } From c43d606f8e7019131add5140076ddf5ba7a271d7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 21:42:04 -0400 Subject: [PATCH 070/378] agent: use Effect schema for generated agent object (#26973) --- packages/opencode/src/agent/agent.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index d96e508c9..b9b56396f 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -1,5 +1,4 @@ import { Config } from "@/config/config" -import z from "zod" import { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "../provider/schema" import { generateObject, streamObject, type ModelMessage } from "ai" @@ -49,6 +48,12 @@ export const Info = Schema.Struct({ }).annotate({ identifier: "Agent" }) export type Info = DeepMutable> +const GeneratedAgent = Schema.Struct({ + identifier: Schema.String, + whenToUse: Schema.String, + systemPrompt: Schema.String, +}) + export interface Interface { readonly get: (agent: string) => Effect.Effect readonly list: () => Effect.Effect @@ -405,11 +410,10 @@ export const layer = Layer.effect( }, ], model: language, - schema: z.object({ - identifier: z.string(), - whenToUse: z.string(), - systemPrompt: z.string(), - }), + schema: Object.assign( + Schema.toStandardSchemaV1(GeneratedAgent), + Schema.toStandardJSONSchemaV1(GeneratedAgent), + ), } satisfies Parameters[0] if (isOpenaiOauth) { From ce720207500f66127629d1e9f7209324d8b6370f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 21:42:17 -0400 Subject: [PATCH 071/378] test(tool): migrate edit tests to Effect runner (#26977) --- packages/opencode/test/tool/edit.test.ts | 996 +++++++++-------------- 1 file changed, 399 insertions(+), 597 deletions(-) diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index a629ff07d..572fcd9aa 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -1,19 +1,20 @@ import { afterAll, afterEach, describe, test, expect } from "bun:test" import path from "path" import fs from "fs/promises" -import { Effect, Layer, ManagedRuntime } from "effect" +import { Cause, Deferred, Effect, Exit, Layer, ManagedRuntime } from "effect" import { EditTool } from "../../src/tool/edit" -import { Instance } from "../../src/project/instance" import { WithInstance } from "../../src/project/with-instance" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, TestInstance, tmpdir } from "../fixture/fixture" import { LSP } from "@/lsp/lsp" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Format } from "../../src/format" import { Agent } from "../../src/agent/agent" import { Bus } from "../../src/bus" -import { BusEvent } from "../../src/bus/bus-event" import { Truncate } from "@/tool/truncate" import { SessionID, MessageID } from "../../src/session/schema" +import * as Tool from "../../src/tool/tool" +import { testEffect } from "../lib/effect" +import { FileWatcher } from "../../src/file/watcher" const ctx = { sessionID: SessionID.make("ses_test-edit-session"), @@ -30,17 +31,19 @@ afterEach(async () => { await disposeAllInstances() }) -const runtime = ManagedRuntime.make( - Layer.mergeAll( - LSP.defaultLayer, - AppFileSystem.defaultLayer, - Format.defaultLayer, - Bus.layer, - Truncate.defaultLayer, - Agent.defaultLayer, - ), +const layer = Layer.mergeAll( + LSP.defaultLayer, + AppFileSystem.defaultLayer, + Format.defaultLayer, + Bus.layer, + Truncate.defaultLayer, + Agent.defaultLayer, ) +const it = testEffect(layer) + +const runtime = ManagedRuntime.make(layer) + afterAll(async () => { await runtime.dispose() }) @@ -53,464 +56,258 @@ const resolve = () => }), ) -const subscribeBus = (def: D, callback: () => unknown) => - runtime.runPromise(Bus.Service.use((bus) => bus.subscribeCallback(def, callback))) - -async function onceBus(def: D) { - const result = Promise.withResolvers() - const unsub = await subscribeBus(def, () => { - unsub() - result.resolve() - }) - return { - wait: result.promise, - unsub, - } -} - -describe("tool.edit", () => { - describe("creating new files", () => { - test("creates new file when oldString is empty", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "newfile.txt") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - const result = await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "", - newString: "new content", - }, - ctx, - ), - ) - - expect(result.metadata.diff).toContain("new content") - - const content = await fs.readFile(filepath, "utf-8") - expect(content).toBe("new content") - }, - }) - }) - - test("preserves BOM when oldString is empty on existing files", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "existing.cs") - const bom = String.fromCharCode(0xfeff) - await fs.writeFile(filepath, `${bom}using System;\n`, "utf-8") +const init = Effect.fn("EditToolTest.init")(function* () { + const info = yield* EditTool + return yield* info.init() +}) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - const result = await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "", - newString: "using Up;\n", - }, - ctx, - ), - ) +const run = Effect.fn("EditToolTest.run")(function* ( + args: Tool.InferParameters, + next: Tool.Context = ctx, +) { + const tool = yield* init() + return yield* tool.execute(args, next) +}) - expect(result.metadata.diff).toContain("-using System;") - expect(result.metadata.diff).toContain("+using Up;") +const fail = Effect.fn("EditToolTest.fail")(function* (args: Tool.InferParameters) { + const exit = yield* run(args).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 edit to fail") +}) - const content = await fs.readFile(filepath, "utf-8") - expect(content.charCodeAt(0)).toBe(0xfeff) - expect(content.slice(1)).toBe("using Up;\n") - }, - }) - }) +const put = Effect.fn("EditToolTest.put")(function* (p: string, content: string) { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(p, content) +}) - test("creates new file with nested directories", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "nested", "dir", "file.txt") +const load = Effect.fn("EditToolTest.load")(function* (p: string) { + const fs = yield* AppFileSystem.Service + return yield* fs.readFileString(p) +}) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "", - newString: "nested file", - }, - ctx, - ), - ) +const loadRaw = Effect.fn("EditToolTest.loadRaw")(function* (p: string) { + return yield* Effect.promise(() => fs.readFile(p, "utf-8")) +}) - const content = await fs.readFile(filepath, "utf-8") - expect(content).toBe("nested file") - }, - }) - }) +const makeDirectory = Effect.fn("EditToolTest.makeDirectory")(function* (p: string) { + const fs = yield* AppFileSystem.Service + yield* fs.makeDirectory(p) +}) - test("emits add event for new files", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "new.txt") +const onceBus = Effect.fn("EditToolTest.onceBus")(function* (def: typeof FileWatcher.Event.Updated) { + const bus = yield* Bus.Service + const deferred = yield* Deferred.make() + const unsub = yield* bus.subscribeCallback(def, () => Effect.runSync(Deferred.succeed(deferred, undefined))) + yield* Effect.addFinalizer(() => Effect.sync(unsub)) + return deferred +}) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const { FileWatcher } = await import("../../src/file/watcher") - - const updated = await onceBus(FileWatcher.Event.Updated) - - try { - const edit = await resolve() - await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "", - newString: "content", - }, - ctx, - ), - ) - - await updated.wait - } finally { - updated.unsub() - } - }, - }) - }) +describe("tool.edit", () => { + describe("creating new files", () => { + it.instance("creates new file when oldString is empty", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "newfile.txt") + const result = yield* run({ filePath: filepath, oldString: "", newString: "new content" }) + + expect(result.metadata.diff).toContain("new content") + expect(yield* load(filepath)).toBe("new content") + }), + ) + + it.instance("preserves BOM when oldString is empty on existing files", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "existing.cs") + const bom = String.fromCharCode(0xfeff) + yield* put(filepath, `${bom}using System;\n`) + + const result = yield* run({ filePath: filepath, oldString: "", newString: "using Up;\n" }) + + expect(result.metadata.diff).toContain("-using System;") + expect(result.metadata.diff).toContain("+using Up;") + + const content = yield* loadRaw(filepath) + expect(content.charCodeAt(0)).toBe(0xfeff) + expect(content.slice(1)).toBe("using Up;\n") + }), + ) + + it.instance("creates new file with nested directories", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "nested", "dir", "file.txt") + + yield* run({ filePath: filepath, oldString: "", newString: "nested file" }) + + expect(yield* load(filepath)).toBe("nested file") + }), + ) + + it.instance("emits add event for new files", () => + Effect.gen(function* () { + const test = yield* TestInstance + const updated = yield* onceBus(FileWatcher.Event.Updated) + + yield* run({ filePath: path.join(test.directory, "new.txt"), oldString: "", newString: "content" }) + yield* Deferred.await(updated) + }), + ) }) describe("editing existing files", () => { - test("replaces text in existing file", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "existing.txt") - await fs.writeFile(filepath, "old content here", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - const result = await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "old content", - newString: "new content", - }, - ctx, - ), - ) - - expect(result.output).toContain("Edit applied successfully") - - const content = await fs.readFile(filepath, "utf-8") - expect(content).toBe("new content here") - }, - }) - }) - - test("replaces the first visible line in BOM files", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "existing.cs") - const bom = String.fromCharCode(0xfeff) - await fs.writeFile(filepath, `${bom}using System;\nclass Test {}\n`, "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - const result = await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "using System;", - newString: "using Up;", - }, - ctx, - ), - ) - - expect(result.metadata.diff).toContain("-using System;") - expect(result.metadata.diff).toContain("+using Up;") - expect(result.metadata.diff).not.toContain(bom) - - const content = await fs.readFile(filepath, "utf-8") - expect(content.charCodeAt(0)).toBe(0xfeff) - expect(content.slice(1)).toBe("using Up;\nclass Test {}\n") - }, - }) - }) - - test("throws error when file does not exist", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "nonexistent.txt") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await expect( - Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "old", - newString: "new", - }, - ctx, - ), - ), - ).rejects.toThrow("not found") - }, - }) - }) - - test("throws error when oldString equals newString", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "content", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await expect( - Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "same", - newString: "same", - }, - ctx, - ), - ), - ).rejects.toThrow("identical") - }, - }) - }) - - test("throws error when oldString not found in file", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "actual content", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await expect( - Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "not in file", - newString: "replacement", - }, - ctx, - ), - ), - ).rejects.toThrow() - }, - }) - }) - - test("replaces all occurrences with replaceAll option", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "foo bar foo baz foo", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "foo", - newString: "qux", - replaceAll: true, - }, - ctx, - ), - ) - - const content = await fs.readFile(filepath, "utf-8") - expect(content).toBe("qux bar qux baz qux") - }, - }) - }) - - test("emits change event for existing files", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "original", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const { FileWatcher } = await import("../../src/file/watcher") - - const updated = await onceBus(FileWatcher.Event.Updated) - - try { - const edit = await resolve() - await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "original", - newString: "modified", - }, - ctx, - ), - ) - - await updated.wait - } finally { - updated.unsub() - } - }, - }) - }) + it.instance("replaces text in existing file", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "existing.txt") + yield* put(filepath, "old content here") + + const result = yield* run({ filePath: filepath, oldString: "old content", newString: "new content" }) + + expect(result.output).toContain("Edit applied successfully") + expect(yield* load(filepath)).toBe("new content here") + }), + ) + + it.instance("replaces the first visible line in BOM files", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "existing.cs") + const bom = String.fromCharCode(0xfeff) + yield* put(filepath, `${bom}using System;\nclass Test {}\n`) + + const result = yield* run({ filePath: filepath, oldString: "using System;", newString: "using Up;" }) + + expect(result.metadata.diff).toContain("-using System;") + expect(result.metadata.diff).toContain("+using Up;") + expect(result.metadata.diff).not.toContain(bom) + + const content = yield* loadRaw(filepath) + expect(content.charCodeAt(0)).toBe(0xfeff) + expect(content.slice(1)).toBe("using Up;\nclass Test {}\n") + }), + ) + + it.instance("throws error when file does not exist", () => + Effect.gen(function* () { + const test = yield* TestInstance + expect( + (yield* fail({ filePath: path.join(test.directory, "nonexistent.txt"), oldString: "old", newString: "new" })) + .message, + ).toContain("not found") + }), + ) + + it.instance("throws error when oldString equals newString", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "content") + + expect((yield* fail({ filePath: filepath, oldString: "same", newString: "same" })).message).toContain( + "identical", + ) + }), + ) + + it.instance("throws error when oldString not found in file", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "actual content") + + expect(yield* fail({ filePath: filepath, oldString: "not in file", newString: "replacement" })).toBeInstanceOf( + Error, + ) + }), + ) + + it.instance("replaces all occurrences with replaceAll option", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "foo bar foo baz foo") + + yield* run({ filePath: filepath, oldString: "foo", newString: "qux", replaceAll: true }) + + expect(yield* load(filepath)).toBe("qux bar qux baz qux") + }), + ) + + it.instance("emits change event for existing files", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "original") + const updated = yield* onceBus(FileWatcher.Event.Updated) + + yield* run({ filePath: filepath, oldString: "original", newString: "modified" }) + yield* Deferred.await(updated) + }), + ) }) describe("edge cases", () => { - test("handles multiline replacements", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "line1\nline2\nline3", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "line2", - newString: "new line 2\nextra line", - }, - ctx, - ), - ) - - const content = await fs.readFile(filepath, "utf-8") - expect(content).toBe("line1\nnew line 2\nextra line\nline3") - }, - }) - }) - - test("handles CRLF line endings", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "line1\r\nold\r\nline3", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "old", - newString: "new", - }, - ctx, - ), - ) - - const content = await fs.readFile(filepath, "utf-8") - expect(content).toBe("line1\r\nnew\r\nline3") - }, - }) - }) - - test("throws error when oldString equals newString", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "content", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await expect( - Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "", - newString: "", - }, - ctx, - ), - ), - ).rejects.toThrow("identical") - }, - }) - }) - - test("throws error when path is directory", async () => { - await using tmp = await tmpdir() - const dirpath = path.join(tmp.path, "adir") - await fs.mkdir(dirpath) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - await expect( - Effect.runPromise( - edit.execute( - { - filePath: dirpath, - oldString: "old", - newString: "new", - }, - ctx, - ), - ), - ).rejects.toThrow("directory") - }, - }) - }) - - test("tracks file diff statistics", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "line1\nline2\nline3", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - const result = await Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "line2", - newString: "new line a\nnew line b", - }, - ctx, - ), - ) - - expect(result.metadata.filediff).toBeDefined() - expect(result.metadata.filediff.file).toBe(filepath) - expect(result.metadata.filediff.additions).toBeGreaterThan(0) - }, - }) - }) + it.instance("handles multiline replacements", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "line1\nline2\nline3") + + yield* run({ filePath: filepath, oldString: "line2", newString: "new line 2\nextra line" }) + + expect(yield* load(filepath)).toBe("line1\nnew line 2\nextra line\nline3") + }), + ) + + it.instance("handles CRLF line endings", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "line1\r\nold\r\nline3") + + yield* run({ filePath: filepath, oldString: "old", newString: "new" }) + + expect(yield* load(filepath)).toBe("line1\r\nnew\r\nline3") + }), + ) + + it.instance("throws error when oldString equals newString", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "content") + + expect((yield* fail({ filePath: filepath, oldString: "", newString: "" })).message).toContain("identical") + }), + ) + + it.instance("throws error when path is directory", () => + Effect.gen(function* () { + const test = yield* TestInstance + const dirpath = path.join(test.directory, "adir") + yield* makeDirectory(dirpath) + + expect((yield* fail({ filePath: dirpath, oldString: "old", newString: "new" })).message).toContain("directory") + }), + ) + + it.instance("tracks file diff statistics", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "line1\nline2\nline3") + + const result = yield* run({ filePath: filepath, oldString: "line2", newString: "new line a\nnew line b" }) + + expect(result.metadata.filediff).toBeDefined() + expect(result.metadata.filediff.file).toBe(filepath) + expect(result.metadata.filediff.additions).toBeGreaterThan(0) + }), + ) }) describe("line endings", () => { @@ -552,149 +349,154 @@ describe("tool.edit", () => { replaceAll?: boolean } - const apply = async (input: Input) => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "test.txt"), input.content) - }, - }) - - return await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - const filePath = path.join(tmp.path, "test.txt") - await Effect.runPromise( - edit.execute( - { - filePath, - oldString: input.oldString, - newString: input.newString, - replaceAll: input.replaceAll, - }, - ctx, - ), - ) - return await Bun.file(filePath).text() - }, - }) - } - - test("preserves LF with LF multi-line strings", async () => { - const content = normalize(old + "\n", "\n") - const output = await apply({ - content, - oldString: normalize(old, "\n"), - newString: normalize(next, "\n"), - }) - expect(output).toBe(normalize(next + "\n", "\n")) - expectLf(output) - }) - - test("preserves CRLF with CRLF multi-line strings", async () => { - const content = normalize(old + "\n", "\r\n") - const output = await apply({ - content, - oldString: normalize(old, "\r\n"), - newString: normalize(next, "\r\n"), - }) - expect(output).toBe(normalize(next + "\n", "\r\n")) - expectCrlf(output) - }) - - test("preserves LF when old/new use CRLF", async () => { - const content = normalize(old + "\n", "\n") - const output = await apply({ - content, - oldString: normalize(old, "\r\n"), - newString: normalize(next, "\r\n"), - }) - expect(output).toBe(normalize(next + "\n", "\n")) - expectLf(output) - }) - - test("preserves CRLF when old/new use LF", async () => { - const content = normalize(old + "\n", "\r\n") - const output = await apply({ - content, - oldString: normalize(old, "\n"), - newString: normalize(next, "\n"), - }) - expect(output).toBe(normalize(next + "\n", "\r\n")) - expectCrlf(output) - }) - - test("preserves LF when newString uses CRLF", async () => { - const content = normalize(old + "\n", "\n") - const output = await apply({ - content, - oldString: normalize(old, "\n"), - newString: normalize(next, "\r\n"), - }) - expect(output).toBe(normalize(next + "\n", "\n")) - expectLf(output) - }) - - test("preserves CRLF when newString uses LF", async () => { - const content = normalize(old + "\n", "\r\n") - const output = await apply({ - content, - oldString: normalize(old, "\r\n"), - newString: normalize(next, "\n"), - }) - expect(output).toBe(normalize(next + "\n", "\r\n")) - expectCrlf(output) - }) - - test("preserves LF with mixed old/new line endings", async () => { - const content = normalize(old + "\n", "\n") - const output = await apply({ - content, - oldString: "alpha\nbeta\r\ngamma", - newString: "alpha\r\nbeta\nomega", + const apply = Effect.fn("EditToolTest.lineEndings.apply")(function* (input: Input) { + const test = yield* TestInstance + const filePath = path.join(test.directory, "test.txt") + yield* put(filePath, input.content) + yield* run({ + filePath, + oldString: input.oldString, + newString: input.newString, + replaceAll: input.replaceAll, }) - expect(output).toBe(normalize(alt + "\n", "\n")) - expectLf(output) + return yield* load(filePath) }) - test("preserves CRLF with mixed old/new line endings", async () => { - const content = normalize(old + "\n", "\r\n") - const output = await apply({ - content, - oldString: "alpha\r\nbeta\ngamma", - newString: "alpha\nbeta\r\nomega", - }) - expect(output).toBe(normalize(alt + "\n", "\r\n")) - expectCrlf(output) - }) - - test("replaceAll preserves LF for multi-line blocks", async () => { - const blockOld = "alpha\nbeta" - const blockNew = "alpha\nbeta-updated" - const content = normalize(blockOld + "\n" + blockOld + "\n", "\n") - const output = await apply({ - content, - oldString: normalize(blockOld, "\n"), - newString: normalize(blockNew, "\n"), - replaceAll: true, - }) - expect(output).toBe(normalize(blockNew + "\n" + blockNew + "\n", "\n")) - expectLf(output) - }) - - test("replaceAll preserves CRLF for multi-line blocks", async () => { - const blockOld = "alpha\nbeta" - const blockNew = "alpha\nbeta-updated" - const content = normalize(blockOld + "\n" + blockOld + "\n", "\r\n") - const output = await apply({ - content, - oldString: normalize(blockOld, "\r\n"), - newString: normalize(blockNew, "\r\n"), - replaceAll: true, - }) - expect(output).toBe(normalize(blockNew + "\n" + blockNew + "\n", "\r\n")) - expectCrlf(output) - }) + it.instance("preserves LF with LF multi-line strings", () => + Effect.gen(function* () { + const content = normalize(old + "\n", "\n") + const output = yield* apply({ + content, + oldString: normalize(old, "\n"), + newString: normalize(next, "\n"), + }) + expect(output).toBe(normalize(next + "\n", "\n")) + expectLf(output) + }), + ) + + it.instance("preserves CRLF with CRLF multi-line strings", () => + Effect.gen(function* () { + const content = normalize(old + "\n", "\r\n") + const output = yield* apply({ + content, + oldString: normalize(old, "\r\n"), + newString: normalize(next, "\r\n"), + }) + expect(output).toBe(normalize(next + "\n", "\r\n")) + expectCrlf(output) + }), + ) + + it.instance("preserves LF when old/new use CRLF", () => + Effect.gen(function* () { + const content = normalize(old + "\n", "\n") + const output = yield* apply({ + content, + oldString: normalize(old, "\r\n"), + newString: normalize(next, "\r\n"), + }) + expect(output).toBe(normalize(next + "\n", "\n")) + expectLf(output) + }), + ) + + it.instance("preserves CRLF when old/new use LF", () => + Effect.gen(function* () { + const content = normalize(old + "\n", "\r\n") + const output = yield* apply({ + content, + oldString: normalize(old, "\n"), + newString: normalize(next, "\n"), + }) + expect(output).toBe(normalize(next + "\n", "\r\n")) + expectCrlf(output) + }), + ) + + it.instance("preserves LF when newString uses CRLF", () => + Effect.gen(function* () { + const content = normalize(old + "\n", "\n") + const output = yield* apply({ + content, + oldString: normalize(old, "\n"), + newString: normalize(next, "\r\n"), + }) + expect(output).toBe(normalize(next + "\n", "\n")) + expectLf(output) + }), + ) + + it.instance("preserves CRLF when newString uses LF", () => + Effect.gen(function* () { + const content = normalize(old + "\n", "\r\n") + const output = yield* apply({ + content, + oldString: normalize(old, "\r\n"), + newString: normalize(next, "\n"), + }) + expect(output).toBe(normalize(next + "\n", "\r\n")) + expectCrlf(output) + }), + ) + + it.instance("preserves LF with mixed old/new line endings", () => + Effect.gen(function* () { + const content = normalize(old + "\n", "\n") + const output = yield* apply({ + content, + oldString: "alpha\nbeta\r\ngamma", + newString: "alpha\r\nbeta\nomega", + }) + expect(output).toBe(normalize(alt + "\n", "\n")) + expectLf(output) + }), + ) + + it.instance("preserves CRLF with mixed old/new line endings", () => + Effect.gen(function* () { + const content = normalize(old + "\n", "\r\n") + const output = yield* apply({ + content, + oldString: "alpha\r\nbeta\ngamma", + newString: "alpha\nbeta\r\nomega", + }) + expect(output).toBe(normalize(alt + "\n", "\r\n")) + expectCrlf(output) + }), + ) + + it.instance("replaceAll preserves LF for multi-line blocks", () => + Effect.gen(function* () { + const blockOld = "alpha\nbeta" + const blockNew = "alpha\nbeta-updated" + const content = normalize(blockOld + "\n" + blockOld + "\n", "\n") + const output = yield* apply({ + content, + oldString: normalize(blockOld, "\n"), + newString: normalize(blockNew, "\n"), + replaceAll: true, + }) + expect(output).toBe(normalize(blockNew + "\n" + blockNew + "\n", "\n")) + expectLf(output) + }), + ) + + it.instance("replaceAll preserves CRLF for multi-line blocks", () => + Effect.gen(function* () { + const blockOld = "alpha\nbeta" + const blockNew = "alpha\nbeta-updated" + const content = normalize(blockOld + "\n" + blockOld + "\n", "\r\n") + const output = yield* apply({ + content, + oldString: normalize(blockOld, "\r\n"), + newString: normalize(blockNew, "\r\n"), + replaceAll: true, + }) + expect(output).toBe(normalize(blockNew + "\n" + blockNew + "\n", "\r\n")) + expectCrlf(output) + }), + ) }) describe("concurrent editing", () => { From 0f5d4ae648241e459e24bc825190fac575dcffa6 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 22:12:07 -0400 Subject: [PATCH 072/378] test(project): stabilize VCS branch update test (#26979) --- packages/opencode/test/project/vcs.test.ts | 27 +++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/opencode/test/project/vcs.test.ts b/packages/opencode/test/project/vcs.test.ts index 75d1feadd..b1d637302 100644 --- a/packages/opencode/test/project/vcs.test.ts +++ b/packages/opencode/test/project/vcs.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect } from "bun:test" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { parsePatch } from "diff" -import { Deferred, Effect, Layer, Stream } from "effect" +import { Deferred, Effect, Layer } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import fs from "fs/promises" import path from "path" @@ -50,13 +50,26 @@ const nextBranchUpdate = Effect.fn("VcsTest.nextBranchUpdate")(function* () { const bus = yield* Bus.Service const updated = yield* Deferred.make() - yield* Stream.runForEach(bus.subscribe(Vcs.Event.BranchUpdated), (evt) => - Deferred.succeed(updated, evt.properties.branch), - ).pipe(Effect.forkScoped) + const off = yield* bus.subscribeCallback(Vcs.Event.BranchUpdated, (evt) => { + Effect.runSync(Deferred.succeed(updated, evt.properties.branch)) + }) + yield* Effect.addFinalizer(() => Effect.sync(off)) return updated }) +const publishHeadChangeUntil = Effect.fn("VcsTest.publishHeadChangeUntil")(function* ( + pending: Deferred.Deferred, + head: string, +) { + const bus = yield* Bus.Service + for (let i = 0; i < 50; i++) { + yield* bus.publish(FileWatcher.Event.Updated, { file: head, event: "change" }) + if (yield* Deferred.isDone(pending)) return + yield* Effect.sleep("10 millis") + } +}) + // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- @@ -99,11 +112,10 @@ describe("Vcs", () => { const vcs = yield* init() yield* vcs.branch() const pending = yield* nextBranchUpdate() - const bus = yield* Bus.Service const head = path.join(test.directory, ".git", "HEAD") yield* write(head, `ref: refs/heads/${branch}\n`) - yield* bus.publish(FileWatcher.Event.Updated, { file: head, event: "change" }) + yield* publishHeadChangeUntil(pending, head) const updated = yield* Deferred.await(pending).pipe(Effect.timeout("2 seconds")) expect(updated).toBe(branch) @@ -122,11 +134,10 @@ describe("Vcs", () => { const vcs = yield* init() yield* vcs.branch() const pending = yield* nextBranchUpdate() - const bus = yield* Bus.Service const head = path.join(test.directory, ".git", "HEAD") yield* write(head, `ref: refs/heads/${branch}\n`) - yield* bus.publish(FileWatcher.Event.Updated, { file: head, event: "change" }) + yield* publishHeadChangeUntil(pending, head) yield* Deferred.await(pending).pipe(Effect.timeout("2 seconds")) const current = yield* vcs.branch() From cc1835e0dbc9ce9f4dbbd92dbc01a30518d0cc40 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 11 May 2026 22:23:52 -0400 Subject: [PATCH 073/378] test(provider): migrate config-backed cases to Effect runner (#26969) --- .../opencode/test/provider/provider.test.ts | 133 ++++++++---------- 1 file changed, 60 insertions(+), 73 deletions(-) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index cdb9d2057..ea65c90c4 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -15,6 +15,7 @@ import { Env } from "../../src/env" import { Effect } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" import { makeRuntime } from "../../src/effect/run-service" +import { testEffect } from "../lib/effect" const env = makeRuntime(Env.Service, Env.defaultLayer) const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v)) @@ -70,6 +71,8 @@ function paid(providers: Awaited>) { return Object.values(item.models).filter((model) => model.cost.input > 0).length } +const it = testEffect(Provider.defaultLayer) + test("provider loaded from env variable", async () => { await using tmp = await tmpdir({ init: async (dir) => { @@ -515,85 +518,69 @@ test("defaultModel respects config model setting", async () => { }) }) -test("provider with baseURL from config", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - provider: { - "custom-openai": { - name: "Custom OpenAI", - npm: "@ai-sdk/openai-compatible", - env: [], - models: { - "gpt-4": { - name: "GPT-4", - tool_call: true, - limit: { context: 128000, output: 4096 }, - }, - }, - options: { - apiKey: "test-key", - baseURL: "https://custom.openai.com/v1", - }, +it.instance( + "provider with baseURL from config", + Effect.gen(function* () { + const providers = yield* Provider.Service.use((provider) => provider.list()) + expect(providers[ProviderID.make("custom-openai")]).toBeDefined() + expect(providers[ProviderID.make("custom-openai")].options.baseURL).toBe("https://custom.openai.com/v1") + }), + { + config: { + provider: { + "custom-openai": { + name: "Custom OpenAI", + npm: "@ai-sdk/openai-compatible", + env: [], + models: { + "gpt-4": { + name: "GPT-4", + tool_call: true, + limit: { context: 128000, output: 4096 }, }, }, - }), - ) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const providers = await list() - expect(providers[ProviderID.make("custom-openai")]).toBeDefined() - expect(providers[ProviderID.make("custom-openai")].options.baseURL).toBe("https://custom.openai.com/v1") + options: { + apiKey: "test-key", + baseURL: "https://custom.openai.com/v1", + }, + }, + }, }, - }) -}) - -test("model cost defaults to zero when not specified", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - provider: { - "test-provider": { - name: "Test Provider", - npm: "@ai-sdk/openai-compatible", - env: [], - models: { - "test-model": { - name: "Test Model", - tool_call: true, - limit: { context: 128000, output: 4096 }, - }, - }, - options: { - apiKey: "test-key", - }, + }, +) + +it.instance( + "model cost defaults to zero when not specified", + Effect.gen(function* () { + const providers = yield* Provider.Service.use((provider) => provider.list()) + const model = providers[ProviderID.make("test-provider")].models["test-model"] + expect(model.cost.input).toBe(0) + expect(model.cost.output).toBe(0) + expect(model.cost.cache.read).toBe(0) + expect(model.cost.cache.write).toBe(0) + }), + { + config: { + provider: { + "test-provider": { + name: "Test Provider", + npm: "@ai-sdk/openai-compatible", + env: [], + models: { + "test-model": { + name: "Test Model", + tool_call: true, + limit: { context: 128000, output: 4096 }, }, }, - }), - ) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const providers = await list() - const model = providers[ProviderID.make("test-provider")].models["test-model"] - expect(model.cost.input).toBe(0) - expect(model.cost.output).toBe(0) - expect(model.cost.cache.read).toBe(0) - expect(model.cost.cache.write).toBe(0) + options: { + apiKey: "test-key", + }, + }, + }, }, - }) -}) + }, +) test("model options are merged from existing model", async () => { await using tmp = await tmpdir({ From 78a2639e5ed83571282268e449b812ba5f1a2f05 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 12 May 2026 10:38:21 +0800 Subject: [PATCH 074/378] fix(app): open next project when closing current one (#26987) --- packages/app/src/pages/layout.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 45fcc6ee2..68a17f73c 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1409,19 +1409,20 @@ export default function Layout(props: ParentProps) { const index = list.findIndex((x) => pathKey(x.worktree) === key) const active = pathKey(currentProject()?.worktree ?? "") === key if (index === -1) return - const next = list[index + 1] if (!active) { layout.projects.close(directory) return } - if (!next) { + if (list.length === 1) { layout.projects.close(directory) navigate("/") return } + const next = list[index + 1] ?? list[index - 1] + navigateWithSidebarReset(`/${base64Encode(next.worktree)}/session`) layout.projects.close(directory) queueMicrotask(() => { From 871374804f1ed989dc49b0a296ce39f0df4ca588 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 12 May 2026 10:39:36 +0800 Subject: [PATCH 075/378] fix(app): use keyed Show for project in layout (#26985) --- packages/app/src/pages/layout.tsx | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 68a17f73c..11bc4fdb5 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -2097,6 +2097,7 @@ export default function Layout(props: ParentProps) {
} + keyed > {(project) => ( <> @@ -2107,9 +2108,7 @@ export default function Layout(props: ParentProps) { id={`project:${projectId()}`} value={projectName} onSave={(next) => { - const item = project() - if (!item) return - void renameProject(item, next) + void renameProject(project, next) }} class="text-14-medium text-text-strong truncate" displayClass="text-14-medium text-text-strong truncate" @@ -2151,9 +2150,7 @@ export default function Layout(props: ParentProps) { { - const item = project() - if (!item) return - showEditProjectDialog(item) + showEditProjectDialog(project) }} > {language.t("common.edit")} @@ -2163,9 +2160,7 @@ export default function Layout(props: ParentProps) { data-project={slug()} disabled={!canToggle()} onSelect={() => { - const item = project() - if (!item) return - toggleProjectWorkspaces(item) + toggleProjectWorkspaces(project) }} > @@ -2224,7 +2219,7 @@ export default function Layout(props: ParentProps) { From c96a77c60b54cf58b162c93f9ffea19fde288168 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:15:38 -0400 Subject: [PATCH 198/378] test(pty): migrate session tests to Effect runner (#27222) --- .../opencode/test/pty/pty-session.test.ts | 167 +++++++++--------- 1 file changed, 80 insertions(+), 87 deletions(-) diff --git a/packages/opencode/test/pty/pty-session.test.ts b/packages/opencode/test/pty/pty-session.test.ts index 8c5d804b7..9c58ab935 100644 --- a/packages/opencode/test/pty/pty-session.test.ts +++ b/packages/opencode/test/pty/pty-session.test.ts @@ -1,103 +1,96 @@ -import { describe, expect, test } from "bun:test" -import { AppRuntime } from "../../src/effect/app-runtime" +import { describe, expect } from "bun:test" import { Bus } from "../../src/bus" -import { Effect } from "effect" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { Config } from "../../src/config/config" +import { Plugin } from "../../src/plugin" import { Pty } from "../../src/pty" import type { PtyID } from "../../src/pty/schema" -import { tmpdir } from "../fixture/fixture" -import { setTimeout as sleep } from "node:timers/promises" +import { Effect, Layer, Queue } from "effect" +import { testEffect } from "../lib/effect" -const wait = async (fn: () => boolean, ms = 5000) => { - const end = Date.now() + ms - while (Date.now() < end) { - if (fn()) return - await sleep(25) - } - throw new Error("timeout waiting for pty events") -} - -const pick = (log: Array<{ type: "created" | "exited" | "deleted"; id: PtyID }>, id: PtyID) => { - return log.filter((evt) => evt.id === id).map((evt) => evt.type) -} - -describe("pty", () => { - test("publishes created, exited, deleted in order for a short-lived process", async () => { - if (process.platform === "win32") return +type PtyEvent = { type: "created" | "exited" | "deleted"; id: PtyID } - await using dir = await tmpdir({ git: true }) +const it = testEffect( + Pty.layer.pipe( + Layer.provideMerge(Bus.layer), + Layer.provideMerge(Config.defaultLayer), + Layer.provideMerge(Plugin.defaultLayer), + ), +) +const ptyTest = process.platform === "win32" ? it.instance.skip : it.instance - await WithInstance.provide({ - directory: dir.path, - fn: () => - AppRuntime.runPromise( - Effect.gen(function* () { - const pty = yield* Pty.Service - const log: Array<{ type: "created" | "exited" | "deleted"; id: PtyID }> = [] - const off = [ - Bus.subscribe(Pty.Event.Created, (evt) => log.push({ type: "created", id: evt.properties.info.id })), - Bus.subscribe(Pty.Event.Exited, (evt) => log.push({ type: "exited", id: evt.properties.id })), - Bus.subscribe(Pty.Event.Deleted, (evt) => log.push({ type: "deleted", id: evt.properties.id })), - ] +const subscribePtyEvents = Effect.fn("PtySessionTest.subscribePtyEvents")(function* () { + const bus = yield* Bus.Service + const events = yield* Queue.unbounded() - let id: PtyID | undefined - try { - const info = yield* pty.create({ - command: "/usr/bin/env", - args: ["sh", "-c", "sleep 0.1"], - title: "sleep", - }) - id = info.id + const subscribe = (effect: Effect.Effect<() => void, never, A>) => + Effect.acquireRelease(effect, (off) => Effect.sync(off)) - yield* Effect.promise(() => wait(() => pick(log, id!).includes("exited"))) + yield* subscribe( + bus.subscribeCallback(Pty.Event.Created, (evt) => { + Queue.offerUnsafe(events, { type: "created", id: evt.properties.info.id }) + }), + ) + yield* subscribe( + bus.subscribeCallback(Pty.Event.Exited, (evt) => { + Queue.offerUnsafe(events, { type: "exited", id: evt.properties.id }) + }), + ) + yield* subscribe( + bus.subscribeCallback(Pty.Event.Deleted, (evt) => { + Queue.offerUnsafe(events, { type: "deleted", id: evt.properties.id }) + }), + ) - yield* pty.remove(id) - yield* Effect.promise(() => wait(() => pick(log, id!).length >= 3)) - expect(pick(log, id!)).toEqual(["created", "exited", "deleted"]) - } finally { - off.forEach((x) => x()) - if (id) yield* pty.remove(id) - } - }), - ), - }) - }) + return events +}) - test("publishes created, exited, deleted in order for /bin/sh + remove", async () => { - if (process.platform === "win32") return +const createPty = Effect.fn("PtySessionTest.createPty")(function* (input: Pty.CreateInput) { + const pty = yield* Pty.Service + return yield* Effect.acquireRelease(pty.create(input), (info) => pty.remove(info.id).pipe(Effect.ignore)) +}) - await using dir = await tmpdir({ git: true }) +const waitForEvents = (events: Queue.Queue, id: PtyID, count: number) => { + return Effect.gen(function* () { + const picked: Array = [] + while (picked.length < count) { + const evt = yield* Queue.take(events) + if (evt.id === id) picked.push(evt.type) + } + return picked + }).pipe( + Effect.timeoutOrElse({ + duration: "5 seconds", + orElse: () => Effect.fail(new Error("timeout waiting for pty events")), + }), + ) +} - await WithInstance.provide({ - directory: dir.path, - fn: () => - AppRuntime.runPromise( - Effect.gen(function* () { - const pty = yield* Pty.Service - const log: Array<{ type: "created" | "exited" | "deleted"; id: PtyID }> = [] - const off = [ - Bus.subscribe(Pty.Event.Created, (evt) => log.push({ type: "created", id: evt.properties.info.id })), - Bus.subscribe(Pty.Event.Exited, (evt) => log.push({ type: "exited", id: evt.properties.id })), - Bus.subscribe(Pty.Event.Deleted, (evt) => log.push({ type: "deleted", id: evt.properties.id })), - ] +describe("pty", () => { + ptyTest("publishes created, exited, deleted in order for a short-lived process", () => + Effect.gen(function* () { + const events = yield* subscribePtyEvents() + const info = yield* createPty({ + command: "/usr/bin/env", + args: ["sh", "-c", "sleep 0.1"], + title: "sleep", + }) - let id: PtyID | undefined - try { - const info = yield* pty.create({ command: "/bin/sh", title: "sh" }) - id = info.id + expect(yield* waitForEvents(events, info.id, 3)).toEqual(["created", "exited", "deleted"]) + }), + { git: true }, + ) - yield* Effect.promise(() => sleep(100)) + ptyTest("publishes created, exited, deleted in order for /bin/sh + remove", () => + Effect.gen(function* () { + const pty = yield* Pty.Service + const events = yield* subscribePtyEvents() + const info = yield* createPty({ command: "/bin/sh", title: "sh" }) - yield* pty.remove(id) - yield* Effect.promise(() => wait(() => pick(log, id!).length >= 3)) - expect(pick(log, id!)).toEqual(["created", "exited", "deleted"]) - } finally { - off.forEach((x) => x()) - if (id) yield* pty.remove(id) - } - }), - ), - }) - }) + expect(yield* waitForEvents(events, info.id, 1)).toEqual(["created"]) + yield* pty.write(info.id, "exit\n") + expect(yield* waitForEvents(events, info.id, 2)).toEqual(["exited", "deleted"]) + yield* pty.remove(info.id) + }), + { git: true }, + ) }) From b43147440c2dcd8c37e7a00017e6d92060097117 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 02:16:48 +0000 Subject: [PATCH 199/378] chore: generate --- .../opencode/test/pty/pty-session.test.ts | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/opencode/test/pty/pty-session.test.ts b/packages/opencode/test/pty/pty-session.test.ts index 9c58ab935..12784baf3 100644 --- a/packages/opencode/test/pty/pty-session.test.ts +++ b/packages/opencode/test/pty/pty-session.test.ts @@ -66,31 +66,35 @@ const waitForEvents = (events: Queue.Queue, id: PtyID, count: number) } describe("pty", () => { - ptyTest("publishes created, exited, deleted in order for a short-lived process", () => - Effect.gen(function* () { - const events = yield* subscribePtyEvents() - const info = yield* createPty({ - command: "/usr/bin/env", - args: ["sh", "-c", "sleep 0.1"], - title: "sleep", - }) + ptyTest( + "publishes created, exited, deleted in order for a short-lived process", + () => + Effect.gen(function* () { + const events = yield* subscribePtyEvents() + const info = yield* createPty({ + command: "/usr/bin/env", + args: ["sh", "-c", "sleep 0.1"], + title: "sleep", + }) - expect(yield* waitForEvents(events, info.id, 3)).toEqual(["created", "exited", "deleted"]) - }), + expect(yield* waitForEvents(events, info.id, 3)).toEqual(["created", "exited", "deleted"]) + }), { git: true }, ) - ptyTest("publishes created, exited, deleted in order for /bin/sh + remove", () => - Effect.gen(function* () { - const pty = yield* Pty.Service - const events = yield* subscribePtyEvents() - const info = yield* createPty({ command: "/bin/sh", title: "sh" }) + ptyTest( + "publishes created, exited, deleted in order for /bin/sh + remove", + () => + Effect.gen(function* () { + const pty = yield* Pty.Service + const events = yield* subscribePtyEvents() + const info = yield* createPty({ command: "/bin/sh", title: "sh" }) - expect(yield* waitForEvents(events, info.id, 1)).toEqual(["created"]) - yield* pty.write(info.id, "exit\n") - expect(yield* waitForEvents(events, info.id, 2)).toEqual(["exited", "deleted"]) - yield* pty.remove(info.id) - }), + expect(yield* waitForEvents(events, info.id, 1)).toEqual(["created"]) + yield* pty.write(info.id, "exit\n") + expect(yield* waitForEvents(events, info.id, 2)).toEqual(["exited", "deleted"]) + yield* pty.remove(info.id) + }), { git: true }, ) }) From 2f4dce789f3376e253308458e51286d00dcb2097 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 13 May 2026 10:27:05 +0800 Subject: [PATCH 200/378] app: use session_working helper to simplify loading states (#27212) --- packages/app/src/components/prompt-input.tsx | 8 +------- packages/app/src/context/global-sync/child-store.ts | 3 +++ packages/app/src/context/global-sync/types.ts | 1 + packages/app/src/pages/layout/sidebar-items.tsx | 4 +--- packages/app/src/pages/layout/sidebar-project.tsx | 2 +- packages/app/src/pages/session.tsx | 5 +---- .../src/pages/session/composer/session-composer-state.ts | 9 +-------- packages/app/src/pages/session/use-session-commands.tsx | 4 +--- 8 files changed, 10 insertions(+), 26 deletions(-) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index eaeedf087..1e1be28b5 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -240,13 +240,7 @@ export const PromptInput: Component = (props) => { return paths }) const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) - const status = createMemo( - () => - sync.data.session_status[params.id ?? ""] ?? { - type: "idle", - }, - ) - const working = createMemo(() => status()?.type !== "idle") + const working = createMemo(() => sync.data.session_working(params.id ?? "")) const imageAttachments = createMemo(() => prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"), ) diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index e8ca597d1..af90fab6b 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -208,6 +208,9 @@ export function createChildStoreManager(input: { session: [], sessionTotal: 0, session_status: {}, + session_working(id: string) { + return this.session_status[id].type !== "idle" + }, session_diff: {}, todo: {}, permission: {}, diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index 6bf42a073..43837ac97 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -46,6 +46,7 @@ export type State = { session_status: { [sessionID: string]: SessionStatus } + session_working(id: string): boolean session_diff: { [sessionID: string]: SnapshotFileDiff[] } diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 3aac5a613..77d9a03d9 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -166,9 +166,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { }) const isWorking = createMemo(() => { if (hasPermissions()) return false - // This matches how the TUI does it - const status = sessionStore.session_status[props.session.id] - return status?.type === "busy" || status?.type === "retry" + return sessionStore.session_working(props.session.id) }) const tint = createMemo(() => messageAgentColor(sessionStore.message[props.session.id], sessionStore.agent)) diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index 58595c25b..b910dd209 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -305,7 +305,7 @@ export const SortableProject = (props: { const isWorking = createMemo(() => dirs().some((directory) => { const [store] = globalSync.child(directory, { bootstrap: false }) - return Object.values(store.session_status).some((status) => status?.type === "busy" || status?.type === "retry") + return Object.keys(store.session_status).some((id) => store.session_working(id)) }), ) const projectSessions = createMemo(() => sortedRootSessions(projectStore(), props.sortNow())) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 8bc7e6a5c..1e73ed590 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1496,10 +1496,7 @@ export default function Page() { return out }) - const busy = (sessionID: string) => { - // This matches how the TUI does it - return (sync.data.session_status[sessionID] ?? { type: "idle" as const }).type !== "idle" - } + const busy = (sessionID: string) => sync.data.session_working(sessionID) const queuedFollowups = createMemo(() => { const id = params.id diff --git a/packages/app/src/pages/session/composer/session-composer-state.ts b/packages/app/src/pages/session/composer/session-composer-state.ts index 525766dcf..a7213c4a7 100644 --- a/packages/app/src/pages/session/composer/session-composer-state.ts +++ b/packages/app/src/pages/session/composer/session-composer-state.ts @@ -57,14 +57,7 @@ export function createSessionComposerState(options?: { closeMs?: number | (() => () => todos().length > 0 && todos().every((todo) => todo.status === "completed" || todo.status === "cancelled"), ) - const status = createMemo(() => { - const id = params.id - if (!id) return idle - return sync.data.session_status[id] ?? idle - }) - - const busy = createMemo(() => status().type !== "idle") - const live = createMemo(() => busy() || blocked()) + const live = createMemo(() => sync.data.session_working(params.id ?? "") || blocked()) const [store, setStore] = createStore({ responding: undefined as string | undefined, diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 922299bec..b45d110b9 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -75,8 +75,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => { import.meta.env.VITE_OPENCODE_CHANNEL !== "beta" || settings.general.showFileTree() - const idle = { type: "idle" as const } - const status = () => sync.data.session_status[params.id ?? ""] ?? idle const messages = () => { const id = params.id if (!id) return [] @@ -290,7 +288,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { const sessionID = params.id if (!sessionID) return - if (status().type !== "idle") { + if (sync.data.session_working(params.id ?? "")) { await sdk.client.session.abort({ sessionID }).catch(() => {}) } From 91a9514ed0d492c4ede303779aa419c22980a16f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:32:30 -0400 Subject: [PATCH 201/378] test(server): use AppFileSystem in provider tests (#27227) --- .../test/server/httpapi-provider.test.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/opencode/test/server/httpapi-provider.test.ts b/packages/opencode/test/server/httpapi-provider.test.ts index 6ea664d63..cb47e5bbd 100644 --- a/packages/opencode/test/server/httpapi-provider.test.ts +++ b/packages/opencode/test/server/httpapi-provider.test.ts @@ -1,6 +1,7 @@ import { describe, expect } from "bun:test" -import { Effect, FileSystem, Layer, Path } from "effect" -import { NodeFileSystem, NodePath } from "@effect/platform-node" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { Effect, Layer } from "effect" +import path from "path" import { Server } from "../../src/server/server" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" @@ -16,7 +17,7 @@ const testStateLayer = Layer.effectDiscard( ), ) -const it = testEffect(Layer.mergeAll(testStateLayer, NodeFileSystem.layer, NodePath.layer)) +const it = testEffect(Layer.mergeAll(testStateLayer, AppFileSystem.defaultLayer)) const projectOptions = { config: { formatter: false, lsp: false } } const providerID = "test-oauth-parity" const oauthURL = "https://example.com/oauth" @@ -95,11 +96,9 @@ function requestAuthorize(input: { function writeProviderAuthPlugin(dir: string) { return Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem - const path = yield* Path.Path + const fs = yield* AppFileSystem.Service - yield* fs.makeDirectory(path.join(dir, ".opencode", "plugin"), { recursive: true }) - yield* fs.writeFileString( + yield* fs.writeWithDirs( path.join(dir, ".opencode", "plugin", "provider-oauth-parity.ts"), [ "export default {", @@ -131,11 +130,9 @@ function writeProviderAuthPlugin(dir: string) { function writeFunctionOptionsPlugin(dir: string) { return Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem - const path = yield* Path.Path + const fs = yield* AppFileSystem.Service - yield* fs.makeDirectory(path.join(dir, ".opencode", "plugin"), { recursive: true }) - yield* fs.writeFileString( + yield* fs.writeWithDirs( path.join(dir, ".opencode", "plugin", "provider-function-options.ts"), [ "export default {", @@ -164,11 +161,9 @@ function writeFunctionOptionsPlugin(dir: string) { function writeProviderModelsMutationPlugin(dir: string) { return Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem - const path = yield* Path.Path + const fs = yield* AppFileSystem.Service - yield* fs.makeDirectory(path.join(dir, ".opencode", "plugin"), { recursive: true }) - yield* fs.writeFileString( + yield* fs.writeWithDirs( path.join(dir, ".opencode", "plugin", "provider-models-mutation.ts"), [ "export default {", From ff16eb8dea2cd9e5d5c680d491da407c2b75f470 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:32:50 -0400 Subject: [PATCH 202/378] test(project): use Deferred for dispose handoff (#27225) --- packages/opencode/test/project/instance.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/opencode/test/project/instance.test.ts b/packages/opencode/test/project/instance.test.ts index 167c7680c..9c0f9150e 100644 --- a/packages/opencode/test/project/instance.test.ts +++ b/packages/opencode/test/project/instance.test.ts @@ -1,6 +1,6 @@ import { describe, expect } from "bun:test" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { Deferred, Effect, Fiber, Layer, Queue } from "effect" +import { Deferred, Effect, Fiber, Layer } from "effect" import { InstanceRef } from "../../src/effect/instance-ref" import { registerDisposer } from "../../src/effect/instance-registry" import { InstanceBootstrap } from "../../src/project/bootstrap-service" @@ -203,20 +203,20 @@ describe("InstanceStore", () => { const dir = yield* tmpdirScoped({ git: true }) const store = yield* InstanceStore.Service const disposing = yield* Deferred.make() - const releaseDispose = yield* Queue.unbounded<() => void>() + const releaseDispose = yield* Deferred.make<() => void>() const disposed: Array = [] yield* registerDisposerScoped((directory) => { disposed.push(directory) Deferred.doneUnsafe(disposing, Effect.void) return new Promise((resolve) => { - Queue.offerUnsafe(releaseDispose, resolve) + Deferred.doneUnsafe(releaseDispose, Effect.succeed(resolve)) }) }) yield* store.load({ directory: dir }) const first = yield* store.disposeAll().pipe(Effect.forkScoped) yield* Deferred.await(disposing) - const release = yield* Queue.take(releaseDispose) + const release = yield* Deferred.await(releaseDispose) const second = yield* store.disposeAll().pipe(Effect.forkScoped) expect(disposed).toEqual([dir]) From 80543fb5dc1e22d922c03e2057ef50869e8cb263 Mon Sep 17 00:00:00 2001 From: qunqin <146629280+qwq202@users.noreply.github.com> Date: Wed, 13 May 2026 10:40:36 +0800 Subject: [PATCH 203/378] fix(desktop): resolve login shell when loading env (#26449) Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> --- packages/desktop/src/main/shell-env.test.ts | 9 ++++++++- packages/desktop/src/main/shell-env.ts | 12 +++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/desktop/src/main/shell-env.test.ts b/packages/desktop/src/main/shell-env.test.ts index cfe88277e..e71708ad0 100644 --- a/packages/desktop/src/main/shell-env.test.ts +++ b/packages/desktop/src/main/shell-env.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" -import { isNushell, mergeShellEnv, parseShellEnv } from "./shell-env" +import { isNushell, mergeShellEnv, parseShellEnv, resolveUserShell } from "./shell-env" describe("shell env", () => { test("parseShellEnv supports null-delimited pairs", () => { @@ -34,6 +34,13 @@ describe("shell env", () => { expect(env.OPENCODE_CLIENT).toBe("desktop") }) + test("resolveUserShell falls back to the login shell before /bin/sh", () => { + expect(resolveUserShell("/custom/env-shell", "/bin/zsh")).toBe("/custom/env-shell") + expect(resolveUserShell(undefined, "/bin/zsh")).toBe("/bin/zsh") + expect(resolveUserShell(undefined, "unknown")).toBe("/bin/sh") + expect(resolveUserShell(undefined, undefined)).toBe("/bin/sh") + }) + test("isNushell handles path and binary name", () => { expect(isNushell("nu")).toBe(true) expect(isNushell("/opt/homebrew/bin/nu")).toBe(true) diff --git a/packages/desktop/src/main/shell-env.ts b/packages/desktop/src/main/shell-env.ts index 4a65fbf0f..deb43033a 100644 --- a/packages/desktop/src/main/shell-env.ts +++ b/packages/desktop/src/main/shell-env.ts @@ -1,4 +1,5 @@ import { spawnSync } from "node:child_process" +import { userInfo } from "node:os" import { basename } from "node:path" import { getLogger } from "./logging" @@ -6,8 +7,17 @@ const TIMEOUT = 5_000 type Probe = { type: "Loaded"; value: Record } | { type: "Timeout" } | { type: "Unavailable" } +export function resolveUserShell(envShell: string | undefined, loginShell: string | null | undefined) { + const resolvedLoginShell = loginShell && loginShell !== "unknown" ? loginShell : undefined + return envShell || resolvedLoginShell || "/bin/sh" +} + export function getUserShell() { - return process.env.SHELL || "/bin/sh" + try { + return resolveUserShell(process.env.SHELL, userInfo().shell) + } catch { + return resolveUserShell(process.env.SHELL, undefined) + } } export function parseShellEnv(out: Buffer) { From 588b5240d04c917eb39d6f4e1865b42c01e05eec Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:43:41 -0400 Subject: [PATCH 204/378] test(server): migrate worktree endpoint repro to effect runner (#27220) --- .../server/worktree-endpoint-repro.test.ts | 162 ++++++++++++++---- 1 file changed, 132 insertions(+), 30 deletions(-) diff --git a/packages/opencode/test/server/worktree-endpoint-repro.test.ts b/packages/opencode/test/server/worktree-endpoint-repro.test.ts index e95d706d5..a1f6bf45a 100644 --- a/packages/opencode/test/server/worktree-endpoint-repro.test.ts +++ b/packages/opencode/test/server/worktree-endpoint-repro.test.ts @@ -1,13 +1,14 @@ import { describe, expect } from "bun:test" -import { Effect, Layer } from "effect" +import { Effect, Layer, Queue } from "effect" import { HttpRouter } from "effect/unstable/http" import { Flag } from "@opencode-ai/core/flag/flag" +import { GlobalBus, type GlobalEvent } from "@/bus/global" +import { Worktree } from "@/worktree" import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/groups/experimental" import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/groups/workspace" -import { withTimeout } from "../../src/util/timeout" import { resetDatabase } from "../fixture/db" -import { TestInstance } from "../fixture/fixture" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" const stateLayer = Layer.effectDiscard( @@ -28,7 +29,10 @@ const stateLayer = Layer.effectDiscard( ) const it = testEffect(stateLayer) +const worktreeTest = process.platform === "win32" ? it.instance.skip : it.instance type TestServer = ReturnType +type CreatedWorktree = { directory: string } +type ScopedWorktree = { directory: string; body: CreatedWorktree; ready: Effect.Effect } function serverScoped() { return Effect.acquireRelease( @@ -44,14 +48,106 @@ function request(server: TestServer, input: string, init?: RequestInit) { } function withRequestTimeout(effect: Effect.Effect, label: string, ms = 5_000) { - return Effect.promise(() => withTimeout(Effect.runPromise(effect), ms, label)) + return effect.pipe( + Effect.timeoutOrElse({ + duration: `${ms} millis`, + orElse: () => Effect.fail(new Error(`${label} timed out after ${ms}ms`)), + }), + ) +} + +function json(response: Response) { + return Effect.promise(() => response.json() as Promise) +} + +function readyWatcher() { + return Effect.gen(function* () { + const events = yield* Queue.bounded(1) + const on = (event: GlobalEvent) => { + if (event.payload.type === Worktree.Event.Ready.type) Queue.offerUnsafe(events, event) + } + + GlobalBus.on("event", on) + yield* Effect.addFinalizer(() => Effect.sync(() => GlobalBus.off("event", on))) + + return (directory: string) => + Effect.gen(function* () { + while (true) { + const event = yield* Queue.take(events) + if (event.directory === directory) return + } + }).pipe( + Effect.timeoutOrElse({ + duration: "10 seconds", + orElse: () => Effect.fail(new Error(`timed out waiting for worktree.ready: ${directory}`)), + }), + ) + }) +} + +function removeCreatedWorktree(input: { + server: TestServer + rootDirectory: string + worktreeDirectory: string + ready: Effect.Effect +}) { + return Effect.gen(function* () { + yield* input.ready.pipe(Effect.timeout("1 second"), Effect.ignore) + yield* Effect.promise(() => disposeAllInstances()).pipe(Effect.ignore) + + const removed = yield* request( + input.server, + `${ExperimentalPaths.worktree}?directory=${encodeURIComponent(input.rootDirectory)}`, + { + method: "DELETE", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ directory: input.worktreeDirectory }), + }, + ) + if (removed.status !== 200) { + const message = yield* Effect.promise(() => removed.text()) + throw new Error(`failed to remove worktree: ${removed.status} ${message}`) + } + const ok = yield* json(removed) + if (!ok) throw new Error(`failed to remove worktree ${input.worktreeDirectory}`) + }) +} + +function createWorktreeScoped(input: { + server: TestServer + directory: string + path: string + init: RequestInit + timeoutLabel: string + timeoutMs?: number +}) { + return Effect.acquireRelease( + Effect.gen(function* () { + const waitReady = yield* readyWatcher() + const response = yield* withRequestTimeout( + request(input.server, input.path, input.init), + input.timeoutLabel, + input.timeoutMs, + ) + expect(response.status).toBe(200) + const body = yield* json(response) + return { directory: body.directory, body, ready: waitReady(body.directory) } satisfies ScopedWorktree + }), + (created) => + removeCreatedWorktree({ + server: input.server, + rootDirectory: input.directory, + worktreeDirectory: created.directory, + ready: created.ready, + }).pipe(Effect.orDie), + ).pipe(Effect.map((created) => created.body)) } function setProjectStartCommand(input: { server: TestServer; directory: string; command: string }) { return Effect.gen(function* () { const current = yield* request(input.server, `/project/current?directory=${encodeURIComponent(input.directory)}`) expect(current.status).toBe(200) - const project = (yield* Effect.promise(() => current.json())) as { id: string } + const project = yield* json<{ id: string }>(current) const updated = yield* request( input.server, `/project/${project.id}?directory=${encodeURIComponent(input.directory)}`, @@ -66,47 +162,51 @@ function setProjectStartCommand(input: { server: TestServer; directory: string; } describe("worktree endpoint reproduction", () => { - it.instance( + worktreeTest( "direct HttpApi worktree create returns without waiting for boot", () => Effect.gen(function* () { const test = yield* TestInstance const server = yield* serverScoped() - const response = yield* withRequestTimeout( - request(server, `${ExperimentalPaths.worktree}?directory=${encodeURIComponent(test.directory)}`, { + const response = yield* createWorktreeScoped({ + server, + directory: test.directory, + path: `${ExperimentalPaths.worktree}?directory=${encodeURIComponent(test.directory)}`, + init: { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}), - }), - "direct worktree create", - ) + }, + timeoutLabel: "direct worktree create", + }) - expect(response.status).toBe(200) - expect(yield* Effect.promise(() => response.json())).toMatchObject({ directory: expect.any(String) }) + expect(response).toMatchObject({ directory: expect.any(String) }) }), { git: true }, ) - it.instance( + worktreeTest( "workspace worktree create does not hang", () => Effect.gen(function* () { const test = yield* TestInstance const server = yield* serverScoped() - const response = yield* withRequestTimeout( - request(server, `${WorkspacePaths.list}?directory=${encodeURIComponent(test.directory)}`, { + const response = yield* createWorktreeScoped({ + server, + directory: test.directory, + path: `${WorkspacePaths.list}?directory=${encodeURIComponent(test.directory)}`, + init: { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ type: "worktree", branch: null }), - }), - "workspace worktree create", - 8_000, - ) + }, + timeoutLabel: "workspace worktree create", + timeoutMs: 8_000, + }) - expect(response.status).toBe(200) - expect(yield* Effect.promise(() => response.json())).toMatchObject({ + expect(response).toMatchObject({ type: "worktree", directory: expect.any(String), }) @@ -114,7 +214,7 @@ describe("worktree endpoint reproduction", () => { { git: true }, ) - it.instance( + worktreeTest( "workspace worktree create returns without waiting for project start command", () => Effect.gen(function* () { @@ -127,17 +227,19 @@ describe("worktree endpoint reproduction", () => { }) const started = Date.now() - const response = yield* withRequestTimeout( - request(server, `${WorkspacePaths.list}?directory=${encodeURIComponent(test.directory)}`, { + yield* createWorktreeScoped({ + server, + directory: test.directory, + path: `${WorkspacePaths.list}?directory=${encodeURIComponent(test.directory)}`, + init: { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ type: "worktree", branch: null }), - }), - "workspace worktree create with project start command", - 6_000, - ) + }, + timeoutLabel: "workspace worktree create with project start command", + timeoutMs: 6_000, + }) - expect(response.status).toBe(200) expect(Date.now() - started).toBeLessThan(1_500) }), { git: true }, From 13fbc9acfc0c664525006294f50674ff7d1cd969 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:45:26 -0400 Subject: [PATCH 205/378] docs(effect): add cleanup roadmap (#27228) --- packages/opencode/specs/effect/errors.md | 362 ++++-------------- packages/opencode/specs/effect/guide.md | 251 ++++++++++++ packages/opencode/specs/effect/migration.md | 321 +++------------- packages/opencode/specs/effect/routes.md | 90 ++--- packages/opencode/specs/effect/schema.md | 359 +++-------------- packages/opencode/specs/effect/todo.md | 237 ++++++++++++ .../opencode/test/EFFECT_TEST_MIGRATION.md | 262 +++++-------- 7 files changed, 807 insertions(+), 1075 deletions(-) create mode 100644 packages/opencode/specs/effect/guide.md create mode 100644 packages/opencode/specs/effect/todo.md diff --git a/packages/opencode/specs/effect/errors.md b/packages/opencode/specs/effect/errors.md index e19199ef4..5266ca510 100644 --- a/packages/opencode/specs/effect/errors.md +++ b/packages/opencode/specs/effect/errors.md @@ -1,41 +1,22 @@ -# Typed error migration +# Typed Error Migration -Plan for moving `packages/opencode` from temporary defect/`NamedError` -compatibility toward typed Effect service errors and explicit HTTP error -contracts. +This note expands the `ERR`, `RENDER`, and `HTTP` tracks from +[`todo.md`](./todo.md). It is the current reference for expected failures, +typed service errors, and HTTP error boundaries. ## Goal - Expected service failures live on the Effect error channel. - Service interfaces expose those failures in their return types. -- Domain errors are authored with Effect Schema so they are reusable by services, - tests, HTTP routes, tools, and OpenAPI generation. -- HTTP status codes and wire compatibility are handled at the HTTP boundary, not - inside service modules. -- `Effect.die`, `throw`, `catchDefect`, and global cause inspection are reserved - for defects, compatibility bridges, or final fallback behavior. +- Domain errors are authored with `Schema.TaggedErrorClass`. +- `Effect.die(...)` is reserved for defects: bugs, impossible states, + violated invariants, and final unknown-boundary fallbacks. +- HTTP status codes and public wire bodies are handled at HTTP route + boundaries, not inside service modules. +- User-facing boundaries render useful structured error details instead of + opaque `Error: SomeName` strings. -## Current State - -- Many migrated services use Effect internally, but expected failures are still a - mix of `NamedError.create(...)`, `namedSchemaError(...)`, `class extends Error`, - `throw`, and `Effect.die(...)`. -- Some services already use `Schema.TaggedErrorClass`, for example `Account`, - `Auth`, `Permission`, `Question`, `Installation`, and parts of - `Workspace`. -- The temporary HttpApi compatibility middleware recognizes `NamedError`, - `Session.BusyError`, and a few name-based cases, then emits the legacy - `{ name, data }` JSON body. -- Effect `HttpApi` only knows how to encode errors that are declared on the - endpoint, group, or middleware. Undeclared expected errors become defects and - eventually fall through to generic HTTP handling. -- The temporary HttpApi error middleware catches defect-wrapped legacy errors to - preserve runtime behavior, but it is intentionally a bridge rather than the - final model. - -## End State - -Service modules own domain failures. +## Service Error Shape ```ts export class SessionBusyError extends Schema.TaggedErrorClass()("SessionBusyError", { @@ -50,281 +31,90 @@ export interface Interface { } ``` -HTTP modules own transport mapping. - -```ts -const get = Effect.fn("SessionHttpApi.get")(function* (ctx: { params: { sessionID: SessionID } }) { - return yield* session - .get(ctx.params.sessionID) - .pipe( - Effect.catchTag("StorageNotFoundError", () => new SessionNotFoundHttpError({ sessionID: ctx.params.sessionID })), - ) -}) -``` - -HTTP-visible error schemas carry their own response status through Effect -HttpApi's `httpApiStatus` annotation. Prefer `HttpApiSchema.status(...)`, or the -equivalent declaration annotation, instead of maintaining a parallel status map. - -```ts -export class SessionNotFoundHttpError extends Schema.TaggedErrorClass()( - "SessionNotFoundHttpError", - { - sessionID: SessionID, - message: Schema.String, - }, - { httpApiStatus: 404 }, -) {} -``` - -Endpoint definitions still declare which HTTP-visible error schemas can be -emitted. The status annotation is only used if the error is part of the endpoint, -group, or middleware error schema and the handler fails with that error on the -typed error channel. +Rules: -```ts -HttpApiEndpoint.get("get", SessionPaths.get, { - success: Session.Info, - error: [SessionNotFoundHttpError, SessionBusyHttpError], -}) -``` - -The service error and HTTP error may be the same class when the wire shape is a -deliberate public contract. They should be different classes when the service -error contains internals, low-level causes, retry hints, or anything that should -not be exposed to API clients. - -## Rules - -- Use `Schema.TaggedErrorClass` for new expected domain errors. -- Include `cause: Schema.optional(Schema.Defect)` only when preserving an - underlying unknown failure is useful for logs or callers. -- Export a domain-level error union from each service module, for example - `export type Error = NotFoundError | BusyError | Storage.Error`. -- Put expected errors in service method signatures, for example - `Effect.Effect`. -- Use `yield* new DomainError(...)` for direct early failures inside +- Use `Schema.TaggedErrorClass` for expected domain failures. +- Export a domain-level `Error` union from each service module. +- Put expected errors in service method signatures. +- Use `yield* new DomainError(...)` for direct early failures in `Effect.gen` / `Effect.fn`. -- Use `Effect.try({ try, catch })`, `Effect.mapError`, or `Effect.catchTag` to - convert external exceptions into domain errors. -- Use `HttpApiSchema.status(...)` or `{ httpApiStatus: code }` on HTTP-visible - error schemas so Effect `HttpApiBuilder` and OpenAPI generation get the status - from the schema itself. -- Do not use `Effect.die(...)` for user, IO, validation, missing-resource, auth, - provider, worktree, or busy-state failures. -- Do not use `catchDefect` to recover expected domain errors. If recovery is - needed, the upstream effect should fail with a typed error instead. -- Do not make service modules import `HttpApiError`, `HttpServerResponse`, HTTP - status codes, or route-specific error schemas. -- Keep raw `HttpRouter` routes free to use `HttpServerRespondable` when that is - the right transport abstraction, but prefer declared `HttpApi` errors for - normal JSON API endpoints. +- Use `Schema.Defect` for unknown cause fields when preserving the cause is + useful for logs or callers. +- Use `Effect.try(...)`, `Effect.tryPromise(...)`, `Effect.mapError`, + `Effect.catchTag`, and `Effect.catchTags` to translate external + failures into domain errors. +- Do not use `throw`, `Effect.die(...)`, or `catchDefect` for expected + user, IO, validation, missing-resource, auth, provider, worktree, or + busy-state failures. ## HTTP Boundary Shape -Create an HttpApi-local error module, likely -`src/server/routes/instance/httpapi/errors.ts`. - -That module should provide: +Service modules stay transport-agnostic. They should not import HTTP +status codes, `HttpApiError`, `HttpServerResponse`, or route-specific +error schemas. -- Legacy-compatible public schemas for `{ name, data }` error bodies that must - remain SDK-compatible while route groups declare typed errors. -- Small constructors or mapping helpers for common API errors such as not found, - bad request, conflict, and unknown internal errors. -- Route-group-specific adapters only when they encode domain-specific public - data. -- A single place to document which public error shape is legacy-compatible and - which shape is new Effect-native API surface. - -Avoid one giant `unknown -> status` mapper. Prefer small, explicit mappers close -to the handler or route group. +HTTP handlers translate service errors into public endpoint errors: ```ts -const mapSessionError = (effect: Effect.Effect) => - effect.pipe( - Effect.catchTag("StorageNotFoundError", (error) => new SessionNotFoundHttpError({ message: error.message })), - Effect.catchTag("SessionBusyError", (error) => new SessionBusyHttpError({ message: error.message })), - ) +const get = Effect.fn("SessionHttpApi.get")(function* (ctx: { params: { sessionID: SessionID } }) { + return yield* session + .get(ctx.params.sessionID) + .pipe(Effect.catchTag("StorageNotFoundError", () => notFound("Session not found"))) +}) ``` -Use built-in `HttpApiError.BadRequest`, `HttpApiError.NotFound`, and related -types only when their generated response body and SDK surface are intentionally -acceptable. Use a custom schema-backed error when clients need the legacy -`{ name, data }` body or a domain-specific error payload. - -## Migration Phases - -### 1. Stabilize The Bridge +Endpoint definitions declare which public errors can be emitted. Public +HTTP error schemas carry their response status with `httpApiStatus` or the +equivalent HttpApi schema annotation. -Keep the temporary HttpApi error middleware only as a compatibility bridge while -typed errors are introduced. +The service error and HTTP error may be the same class only when the wire +shape is intentionally public. Use separate HTTP error schemas when the +service error contains internals, low-level causes, retry hints, or data +that should not be exposed to API clients. -- Add tests that prove the bridge catches legacy `NamedError` defects. -- Add tests that prove declared HttpApi errors still use the declared endpoint - contract. -- Stop returning stack traces in unknown HTTP `500` responses; log the full - `Cause.pretty(cause)` server-side instead. -- Add a comment or TODO that names this plan and states the bridge must shrink - as route groups migrate. +## Mapping Guidance -### 2. Define The Shared HTTP Error Helpers +- Keep one-off translations inline in the handler. +- Extract tiny shared helpers when the same translation repeats across a + route group. +- Do not create one giant `unknown -> status` mapper. +- Do not grow generic HTTP middleware into a registry of domain errors. +- Preserve existing public `{ name, data }` bodies until a deliberate + breaking API change. +- Use built-in `HttpApiError.*` only when its generated body and SDK + surface are intentionally the public contract. -Add the `httpapi/errors.ts` module before converting route groups. +## Middleware Guidance -- Define a legacy `{ name, data }` body helper for SDK-compatible errors. -- Define `UnknownError` for generic internal failures with a safe public message. -- Define `BadRequestError` and `NotFoundError` equivalents only if the actual - wire body must match the existing SDK surface. -- Put the HTTP status on the public schema with `HttpApiSchema.status(...)` or - `{ httpApiStatus: code }`; do not keep a separate name-to-status table. -- Keep conversion helpers pure and small. They should not inspect `Cause` or - accept `unknown` unless they are final fallback helpers. +HTTP middleware should be cross-cutting: auth, context, schema decode +formatting, routing, and final unknown-defect fallback. -### 3. Convert One Vertical Slice +The current compatibility middleware still knows about some legacy domain +errors. As route groups declare expected errors and handlers map them, that +middleware should shrink. It should not gain new name checks. -Start with session read routes because they already have local `mapNotFound` -logic and are heavily covered by existing HttpApi tests. +Unknown `500` responses should log full details server-side with +`Cause.pretty(cause)` and return a safe public body. -- Convert `Session.BusyError` from a plain `Error` to a typed service error, or - add a typed wrapper while preserving the old constructor until callers are - migrated. -- Replace `catchDefect` in `httpapi/handlers/session.ts` with typed error - mapping. -- Add endpoint error schemas for the affected session endpoints. -- Prove behavior with focused tests in `test/server/httpapi-session.test.ts`. -- Remove the migrated cases from the global compatibility middleware. +## Migration Order -### 4. Convert Legacy NamedError Domains - -Move legacy `NamedError.create(...)` services to Effect Schema-backed errors in -small domain PRs. - -Priority order: - -1. `storage/storage.ts` and `storage/db.ts` not-found errors. -2. `worktree/index.ts` `Worktree*` errors. -3. `provider/auth.ts` validation failures and `provider/provider.ts` model-not-found errors. -4. `mcp/index.ts`, `skill/index.ts`, `lsp/client.ts`, and `ide/index.ts` service errors. -5. Config and CLI-only errors after HTTP-facing domains are stable. - -For each domain: - -- Replace `NamedError.create(...)` with `Schema.TaggedErrorClass` when the error - is primarily a service error. -- Keep or add a separate HTTP error schema when the legacy `{ name, data }` wire - shape must remain stable. -- Update service interface return types to include the new error union. -- Replace `throw new X(...)` inside `Effect.fn` with `yield* new X(...)`. -- Replace async exceptions with `Effect.try({ catch })` or explicit `mapError`. -- Add service-level tests that assert the error tag and data, not just the HTTP - status. - -### 5. Declare HttpApi Errors Group By Group - -For each HttpApi group: - -- Inventory every service call and the typed errors it can return. -- Add only the public error schemas that endpoint can actually emit. -- Map service errors to HTTP errors in the handler file. -- Keep built-in `HttpApiError` only for generic request/validation failures where - the generated contract is accepted. -- Update `httpapi/public.ts` compatibility transforms only when the generated - spec cannot represent the desired source shape directly. -- Regenerate the SDK after OpenAPI-visible changes and verify the diff is - intentional. - -Suggested route order: - -1. `session` not-found and busy-state reads. -2. `experimental` worktree mutations. -3. `provider` auth and model selection errors. -4. `mcp` OAuth and connection errors. -5. Remaining route groups as typed error contracts are declared. - -### 6. Remove Defect Recovery - -After enough route groups declare their expected errors: - -- Delete `catchDefect` recovery for domain errors. -- Delete name-prefix checks such as `error.name.startsWith("Worktree")` from - HTTP middleware. -- Delete `NamedError` branches from the Effect HttpApi compatibility middleware - once no Effect route depends on them. -- Leave one final unknown-defect fallback that logs server-side and returns a - safe generic `500` body. - -## Inventory Checklist - -Use this checklist when touching a service or route group. - -- [ ] Does the service interface expose every expected failure in the Effect - error type? -- [ ] Are user-caused, provider-caused, IO, auth, missing-resource, and busy-state - failures modeled as typed errors instead of defects? -- [ ] Does the service avoid importing HTTP status, `HttpApiError`, or response - classes? -- [ ] Does the handler map each service error into a declared endpoint error? -- [ ] Does the endpoint `error` field include every public error the handler can - emit? -- [ ] Does OpenAPI/SDK output either stay byte-identical or have an explicitly - reviewed diff? -- [ ] Do tests cover both service-level error typing and HTTP-level status/body? -- [ ] Did the PR remove any now-unneeded case from the temporary compatibility - middleware? - -## Testing Requirements - -For service conversions: - -- Test the service method directly with `testEffect(...)`. -- Assert on `_tag` or class identity and the structured fields. -- Avoid testing by string-matching `Cause.pretty(...)`. - -For HttpApi conversions: - -- Add or update the focused `test/server/httpapi-*.test.ts` file. -- Assert status code, content type, and exact JSON body for declared public - errors. -- Add a regression test that the temporary middleware is no longer needed for the - migrated route. -- Keep compatibility tests aligned with the existing SDK contract until the - public error shape intentionally changes. - -## Verification Commands - -Run from `packages/opencode` unless noted otherwise. - -```bash -bun run prettier --write -bunx oxlint -bun typecheck -bun run test -- test/server/httpapi-session.test.ts -``` - -Run SDK generation from the repo root when schemas or OpenAPI-visible errors -change. - -```bash -./packages/sdk/js/script/build.ts -``` +Prefer small vertical slices: -## Open Questions +1. Fix rendering at one user-visible boundary. +2. Convert one service domain to `Schema.TaggedErrorClass` errors. +3. Map those errors at the affected HTTP handlers. +4. Remove the corresponding name-based middleware branch if possible. +5. Add or update focused tests for both service error tags and HTTP wire + bodies. -- Should legacy V1 routes keep `{ name, data }` forever while V2 routes expose a - more Effect-native tagged error body? -- Should storage not-found remain generic, or should callers map it to - domain-specific not-found errors before crossing service boundaries? -- Should `namedSchemaError(...)` stay as a long-term public-wire helper, or only - as a migration bridge for old `NamedError` contracts? -- Which SDK version boundary lets us stop remapping built-in Effect HttpApi error - schemas in `httpapi/public.ts`? +Good early domains are storage not-found, worktree errors, and provider +auth validation errors because they currently drive HTTP behavior. -## Success Criteria +## Checklist For A PR -- New service code no longer uses `die` for expected failures. -- A route reviewer can read an endpoint definition and see every public error it - can return. -- The temporary HttpApi error middleware shrinks over time instead of gaining new - name-based cases. -- Service tests prove domain error types without going through HTTP. -- HTTP tests prove status/body contracts without relying on defect recovery. +- [ ] Expected failures are typed errors, not defects. +- [ ] Service method signatures expose the expected error union. +- [ ] HTTP handlers translate domain errors at the boundary. +- [ ] Public HTTP error bodies preserve existing wire contracts. +- [ ] Generic middleware gets smaller or stays unchanged. +- [ ] Focused tests cover the service error and any public HTTP response. diff --git a/packages/opencode/specs/effect/guide.md b/packages/opencode/specs/effect/guide.md new file mode 100644 index 000000000..5df029344 --- /dev/null +++ b/packages/opencode/specs/effect/guide.md @@ -0,0 +1,251 @@ +# Effect Guide + +How we write Effect code in `packages/opencode`. The companion roadmap is +[`todo.md`](./todo.md). + +This guide describes the preferred shape for new work and migrations. If a +legacy file differs, migrate it only when it is already in scope. + +## Service Shape + +Use one module per service: flat top-level exports, traced Effect methods, +explicit layers, and a self-reexport at the bottom. + +```ts +export interface Interface { + readonly get: (id: FooID) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/Foo") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const state = yield* InstanceState.make(Effect.fn("Foo.state")(() => Effect.succeed({}))) + + const get = Effect.fn("Foo.get")(function* (id: FooID) { + const s = yield* InstanceState.get(state) + return yield* loadFoo(s, id) + }) + + return Service.of({ get }) + }), +) + +export const defaultLayer = layer.pipe(Layer.provide(FooDep.defaultLayer)) + +export * as Foo from "./foo" +``` + +Rules: + +- Do not use `export namespace Foo { ... }`. +- Use `Effect.fn("Foo.method")` for public service methods. +- Use `Effect.fnUntraced` for small internal helpers that do not need a + span. +- Keep helpers as non-exported top-level declarations in the same file. +- Self-reexport with `export * as Foo from "."` for `index.ts`, otherwise + `export * as Foo from "./foo"`. +- In `src/config`, keep the existing top-of-file self-export pattern. + +## Runtime Boundaries + +Most code should run through [`AppRuntime`](../../src/effect/app-runtime.ts). +It hosts `AppLayer`, shares the global `memoMap`, and restores the current +instance/workspace refs when crossing from non-Effect code. + +Use `AppRuntime.runPromise(effect)` at app boundaries such as CLI commands, +HTTP handlers, or plain async adapters. + +`makeRuntime(...)` still exists for a few intentional service-local +boundaries and migration leftovers. Do not add a new service-local runtime +unless the service truly cannot live in `AppLayer`. + +## Runtime Flags + +Read opencode runtime flags through +[`RuntimeFlags.Service`](../../src/effect/runtime-flags.ts), not through +mutable `Flag` or late `process.env` reads. + +Tests should vary behavior with explicit layer variants: + +```ts +const it = testEffect(MyService.defaultLayer.pipe(Layer.provide(RuntimeFlags.layer({ experimentalScout: true })))) +``` + +Do not mutate `process.env` or `Flag` after services/layers are built. + +## Per-Instance State + +Use [`InstanceState`](../../src/effect/instance-state.ts) when two open +directories should not share one copy of a service's state. It is backed by +a `ScopedCache`, keyed by directory, and disposed automatically when an +instance is unloaded. + +Put subscriptions, finalizers, and scoped background work inside the +`InstanceState.make(...)` initializer: + +```ts +const cache = + yield * + InstanceState.make( + Effect.fn("Foo.state")(function* () { + const bus = yield* Bus.Service + + yield* bus.subscribeAll().pipe( + Stream.runForEach((event) => handleEvent(event)), + Effect.forkScoped, + ) + + yield* Effect.acquireRelease(openResource, closeResource) + + return yield* loadInitialState() + }), + ) +``` + +Do not add separate `started` flags on top of `InstanceState`. Let +`ScopedCache` handle run-once and deduplication. + +To make `init()` non-blocking, fork at the caller/bootstrap boundary. Do +not fork inside `InstanceState.make(...)` just to return early with +partially initialized state. + +## Errors + +Expected domain failures belong on the Effect error channel. Defects are +for bugs, impossible states, and final unknown-boundary fallbacks. + +```ts +export class SessionBusyError extends Schema.TaggedErrorClass()("SessionBusyError", { + sessionID: SessionID, + message: Schema.String, +}) {} + +export type Error = Storage.Error | SessionBusyError + +export interface Interface { + readonly get: (id: SessionID) => Effect.Effect +} +``` + +Rules: + +- Use `Schema.TaggedErrorClass` for new expected domain errors. +- Export a domain-level `Error` union from service modules. +- In `Effect.gen` / `Effect.fn`, prefer `yield* new MyError(...)` for + direct expected failures. +- Use `Schema.Defect` for unknown cause fields. +- Use `Effect.try(...)`, `Effect.tryPromise(...)`, `Effect.mapError`, + `Effect.catchTag`, and `Effect.catchTags` to translate external + failures into domain errors. +- Do not use `Effect.die(...)` for user, IO, validation, missing-resource, + auth, provider, or busy-state failures. + +## HTTP Error Boundaries + +Service modules stay HTTP-agnostic. They should not import HTTP status +codes, `HttpApiError`, `HttpServerResponse`, or route-specific error +schemas. + +HTTP handlers translate service errors into endpoint-declared public error +schemas. Keep mappings inline when they are one-off; extract tiny shared +helpers only when the same translation repeats. + +Do not turn generic middleware into a registry of domain errors. Middleware +should handle cross-cutting concerns and the final unknown-defect fallback. + +Preserve legacy public wire shapes, such as `{ name, data }`, until a +deliberate breaking API change. + +## Schemas + +Use Effect Schema as the source of truth. + +- Use `Schema.Class` for exported data objects with a clear identity. +- Use `Schema.Struct` for local shapes and simple nested objects. +- Use `Schema.brand` for single-value IDs. +- Reuse named refinements instead of re-spelling constraints. +- Prefer narrow boundary helpers over generic Schema-to-Zod bridges. + +Intentional boundaries: + +- Public plugin tools still expose Zod through `tool.schema = z`. +- Tool parameter JSON Schema is generated through tool-specific helpers. +- Public config and TUI schemas are generated through the schema script. + +## Preferred Services + +In effectified code, yield existing services instead of dropping to ad hoc +platform APIs. + +- Use `AppFileSystem.Service` instead of raw `fs/promises` for app file IO. +- Use `AppProcess.Service` instead of direct `ChildProcessSpawner.spawn` or + legacy process helpers. +- Use `HttpClient.HttpClient` instead of raw `fetch` inside Effect code. +- Use `Path.Path`, `Config`, `Clock`, and `DateTime` when already inside + Effect. +- Use `Effect.callback` for callback-based APIs. +- Use `Effect.void` instead of `Effect.succeed(undefined)`. +- Use `Effect.cached` when concurrent callers should share one in-flight + computation. + +For background loops, use `Effect.repeat` or `Effect.schedule` with +`Effect.forkScoped` in the owning layer/state scope. + +## Promise And ALS Bridges + +[`EffectBridge`](../../src/effect/bridge.ts) is the sanctioned helper for +Promise/callback interop that needs to preserve instance/workspace context. +Keep it, but reduce its dependency on legacy `Instance.current` / +`Instance.restore` over time. + +`Instance.bind` / `Instance.restore` are transitional legacy tools. Use +them only for native callbacks that still require legacy ALS context. Do +not use them for `setTimeout`, `Promise.then`, `EventEmitter.on`, or +Effect fibers. + +## Testing + +Detailed test migration rules live in +[`test/EFFECT_TEST_MIGRATION.md`](../../test/EFFECT_TEST_MIGRATION.md). + +Core pattern: + +```ts +const it = testEffect(Layer.mergeAll(MyService.defaultLayer)) + +describe("my service", () => { + it.instance("does the thing", () => + Effect.gen(function* () { + const svc = yield* MyService.Service + expect(yield* svc.run()).toEqual("ok") + }), + ) +}) +``` + +Rules: + +- Use `it.effect(...)` for TestClock/TestConsole tests. +- Use `it.live(...)` for real timers, filesystem mtimes, child processes, + git, locks, or other live integration behavior. +- Use `it.instance(...)` for service tests that need a scoped instance. +- Prefer Effect-aware fixtures from `test/fixture/fixture.ts`. +- Avoid sleeps; wait for real events or deterministic state transitions. +- Avoid mutable `process.env`, `Flag`, or module-global changes after + layers are built. +- Use `Layer.mock` for partial service stubs. +- Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` test + wrappers. + +## Verification + +From `packages/opencode`: + +```bash +bun run typecheck +bun run test -- path/to/test.ts +``` + +Do not run tests from the repo root; the repo has a guard for that. diff --git a/packages/opencode/specs/effect/migration.md b/packages/opencode/specs/effect/migration.md index 13838e833..5355feccc 100644 --- a/packages/opencode/specs/effect/migration.md +++ b/packages/opencode/specs/effect/migration.md @@ -1,291 +1,62 @@ -# Effect patterns +# Effect Migration Patterns -Practical reference for new and migrated Effect code in `packages/opencode`. +This is the compact reference for moving code toward the current Effect +shape. The high-level roadmap is [`todo.md`](./todo.md); examples and +rules are in [`guide.md`](./guide.md). -## Choose scope +## Default Shape -Use `InstanceState` (from `src/effect/instance-state.ts`) for services that need per-directory state, per-instance cleanup, or project-bound background work. InstanceState uses a `ScopedCache` keyed by directory, so each open project gets its own copy of the state that is automatically cleaned up on disposal. +- Service methods return `Effect`. +- Service methods are named with `Effect.fn("Domain.method")`. +- Expected failures are typed errors on the error channel. +- Dependencies are yielded once at layer construction and closed over by + methods. +- `defaultLayer` wires production dependencies; tests can use open layers + when replacing dependencies. -Use `makeRuntime` (from `src/effect/run-service.ts`) to create a per-service `ManagedRuntime` that lazily initializes and shares layers via a global `memoMap`. Returns `{ runPromise, runFork, runCallback }`. +## Instance State -- Global services (no per-directory state): Account, Auth, AppFileSystem, Installation, Truncate, Worktree -- Instance-scoped (per-directory state via InstanceState): Agent, Bus, Command, Config, File, FileWatcher, Format, LSP, MCP, Permission, Plugin, ProviderAuth, Pty, Question, SessionStatus, Skill, Snapshot, ToolRegistry, Vcs +Use `InstanceState` for per-directory state, subscriptions, scoped +background work, and per-instance cleanup. -Rule of thumb: if two open directories should not share one copy of the service, it needs `InstanceState`. +Do not add ad hoc `started` flags on top of `InstanceState`; the scoped +cache handles run-once and concurrent deduplication. -## Instance context transition +## Runtime Boundaries -See `instance-context.md` for the phased plan to remove the legacy ALS / promise-backed `Instance` helper and move request / CLI / tool boundaries onto Effect-provided instance scope. +Prefer `AppRuntime` for crossing from non-Effect code into the shared app +layer. -## Service shape +`makeRuntime(...)` exists for intentional service-local boundaries and +legacy facades. Do not add new service-local runtimes unless the service is +genuinely outside `AppLayer`. -Every service follows the same pattern: one module, flat top-level exports, traced Effect methods, and a self-reexport at the bottom when the file is the public module. +## Platform Edges -```ts -export interface Interface { - readonly get: (id: FooID) => Effect.Effect -} +- Use `AppFileSystem.Service` instead of raw filesystem APIs in + effectified services. +- Use `AppProcess.Service` instead of raw process wrappers. +- Use `HttpClient.HttpClient` instead of raw `fetch` in Effect code. +- Use `Effect.cached` for shared in-flight work. +- Use `Effect.callback` for callback APIs. -export class Service extends Context.Service()("@opencode/Foo") {} +## Tests During Migration -export const layer = Layer.effect( - Service, - Effect.gen(function* () { - const state = yield* InstanceState.make( - Effect.fn("Foo.state")(() => Effect.succeed({ ... })), - ) +When migrating code, migrate touched tests toward +[`test/EFFECT_TEST_MIGRATION.md`](../../test/EFFECT_TEST_MIGRATION.md): - const get = Effect.fn("Foo.get")(function* (id: FooID) { - const s = yield* InstanceState.get(state) - // ... - }) +- `testEffect(...)` +- `it.effect`, `it.live`, or `it.instance` +- explicit layers for behavior changes +- deterministic waits instead of sleeps +- no mutable env/global flags after layers are built - return Service.of({ get }) - }), -) +## Migration Checklist -export const defaultLayer = layer.pipe(Layer.provide(FooDep.layer)) - -export * as Foo from "." -``` - -Rules: - -- Keep the service surface in one module; prefer flat top-level exports over `export namespace Foo { ... }` -- Use `Effect.fn("Foo.method")` for Effect methods -- Use a self-reexport (`export * as Foo from "."` or `"./foo"`) for the public namespace projection -- Avoid service-local `makeRuntime(...)` facades unless a file is still intentionally in the older migration phase -- No `Layer.fresh` for normal per-directory isolation; use `InstanceState` - -## Schema boundaries - -Use Effect Schema directly at HTTP, tool, and AI SDK boundaries. For provider-facing JSON Schema, use a boundary-specific helper such as `ToolJsonSchema.fromSchema(...)`; do not reintroduce generic Effect Schema → Zod conversion. - -## InstanceState init patterns - -The `InstanceState.make` init callback receives a `Scope`, so you can use `Effect.acquireRelease`, `Effect.addFinalizer`, and `Effect.forkScoped` inside it. Resources acquired this way are automatically cleaned up when the instance is disposed or invalidated by `ScopedCache`. This makes it the right place for: - -- **Subscriptions**: Yield `Bus.Service` at the layer level, then use `Stream` + `forkScoped` inside the init closure. The fiber is automatically interrupted when the instance scope closes: - -```ts -const bus = yield * Bus.Service - -const cache = - yield * - InstanceState.make( - Effect.fn("Foo.state")(function* (ctx) { - // ... load state ... - - yield* bus.subscribeAll().pipe( - Stream.runForEach((event) => - Effect.sync(() => { - /* handle */ - }), - ), - Effect.forkScoped, - ) - - return { - /* state */ - } - }), - ) -``` - -- **Resource cleanup**: Use `Effect.acquireRelease` or `Effect.addFinalizer` for resources that need teardown (native watchers, process handles, etc.): - -```ts -yield * - Effect.acquireRelease( - Effect.sync(() => nativeAddon.watch(dir)), - (watcher) => Effect.sync(() => watcher.close()), - ) -``` - -- **Background fibers**: Use `Effect.forkScoped` — the fiber is interrupted on disposal. -- **Side effects at init**: Config notification, event wiring, etc. all belong in the init closure. Callers just do `InstanceState.get(cache)` to trigger everything, and `ScopedCache` deduplicates automatically. - -The key insight: don't split init into a separate method with a `started` flag. Put everything in the `InstanceState.make` closure and let `ScopedCache` handle the run-once semantics. - -## Effect.cached for deduplication - -Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation. It memoizes the result and deduplicates concurrent fibers — second caller joins the first caller's fiber instead of starting a new one. - -```ts -// Inside the layer — yield* to initialize the memo -let cached = yield * Effect.cached(loadExpensive()) - -const get = Effect.fn("Foo.get")(function* () { - return yield* cached // concurrent callers share the same fiber -}) - -// To invalidate: swap in a fresh memo -const invalidate = Effect.fn("Foo.invalidate")(function* () { - cached = yield* Effect.cached(loadExpensive()) -}) -``` - -Prefer `Effect.cached` over these patterns: - -- Storing a `Fiber.Fiber | undefined` with manual check-and-fork (e.g. `file/index.ts` `ensure`) -- Storing a `Promise` task for deduplication (e.g. `skill/index.ts` `ensure`) -- `let cached: X | undefined` with check-and-load (races when two callers see `undefined` before either resolves) - -`Effect.cached` handles the run-once + concurrent-join semantics automatically. For invalidatable caches, reassign with `yield* Effect.cached(...)` — the old memo is discarded. - -## Scheduled Tasks - -For loops or periodic work, use `Effect.repeat` or `Effect.schedule` with `Effect.forkScoped` in the layer definition. - -## Preferred Effect services - -In effectified services, prefer yielding existing Effect services over dropping down to ad hoc platform APIs. - -Prefer these first: - -- `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O -- `ChildProcessSpawner.ChildProcessSpawner` with `ChildProcess.make(...)` instead of custom process wrappers -- `HttpClient.HttpClient` instead of raw `fetch` -- `Path.Path` instead of mixing path helpers into service code when you already need a path service -- `Config` for effect-native configuration reads -- `Clock` / `DateTime` for time reads inside effects - -## Child processes - -For child process work in services, yield `ChildProcessSpawner.ChildProcessSpawner` in the layer and use `ChildProcess.make(...)`. - -Keep shelling-out code inside the service, not in callers. - -## Shared leaf models - -Shared schema or model files can stay outside the service namespace when lower layers also depend on them. - -That is fine for leaf files like `schema.ts`. Keep the service surface in the owning namespace. - -## Migration checklist - -Service-shape migrated (single namespace, traced methods, `InstanceState` where needed). - -This checklist is only about the service shape migration. Many of these services still keep `makeRuntime(...)` plus async facade exports; that facade-removal phase is tracked separately in `facades.md`. - -- [x] `Account` — `account/index.ts` -- [x] `Agent` — `agent/agent.ts` -- [x] `AppFileSystem` — `filesystem/index.ts` -- [x] `Auth` — `auth/index.ts` (uses `zod()` helper for Schema→Zod interop) -- [x] `Bus` — `bus/index.ts` -- [x] `Command` — `command/index.ts` -- [x] `Config` — `config/config.ts` -- [x] `Discovery` — `skill/discovery.ts` (dependency-only layer, no standalone runtime) -- [x] `File` — `file/index.ts` -- [x] `FileWatcher` — `file/watcher.ts` -- [x] `Format` — `format/index.ts` -- [x] `Installation` — `installation/index.ts` -- [x] `LSP` — `lsp/index.ts` -- [x] `MCP` — `mcp/index.ts` -- [x] `McpAuth` — `mcp/auth.ts` -- [x] `Permission` — `permission/index.ts` -- [x] `Plugin` — `plugin/index.ts` -- [x] `Project` — `project/project.ts` -- [x] `ProviderAuth` — `provider/auth.ts` -- [x] `Pty` — `pty/index.ts` -- [x] `Question` — `question/index.ts` -- [x] `SessionStatus` — `session/status.ts` -- [x] `Skill` — `skill/index.ts` -- [x] `Snapshot` — `snapshot/index.ts` -- [x] `ToolRegistry` — `tool/registry.ts` -- [x] `Truncate` — `tool/truncate.ts` -- [x] `Vcs` — `project/vcs.ts` -- [x] `Worktree` — `worktree/index.ts` - -- [x] `Session` — `session/index.ts` -- [x] `SessionProcessor` — `session/processor.ts` -- [x] `SessionPrompt` — `session/prompt.ts` -- [x] `SessionCompaction` — `session/compaction.ts` -- [x] `SessionSummary` — `session/summary.ts` -- [x] `SessionRevert` — `session/revert.ts` -- [x] `Instruction` — `session/instruction.ts` -- [x] `SystemPrompt` — `session/system.ts` -- [x] `Provider` — `provider/provider.ts` -- [x] `Storage` — `storage/storage.ts` -- [x] `ShareNext` — `share/share-next.ts` -- [x] `SessionTodo` — `session/todo.ts` - -Still open at the service-shape level: - -- [ ] `SyncEvent` — `sync/index.ts` (deferred pending sync with James) -- [ ] `Workspace` — `control-plane/workspace.ts` (deferred pending sync with James) - -## Tool migration - -Tool-specific migration guidance and checklist live in `tools.md`. - -## Effect service adoption in already-migrated code - -Some already-effectified areas still use raw `Filesystem.*` or `Process.spawn` in their implementation or helper modules. These are low-hanging fruit — the layers already exist, they just need the dependency swap. - -### `Filesystem.*` → `AppFileSystem.Service` (yield in layer) - -- [x] `config/config.ts` — `installDependencies()` now uses `AppFileSystem` -- [x] `provider/provider.ts` — recent model state now reads via `AppFileSystem.Service` - -### `Process.spawn` → `ChildProcessSpawner` (yield in layer) - -- [x] `format/formatter.ts` — direct `Process.spawn()` checks removed (`air`, `uv`) -- [ ] `lsp/server.ts` — multiple `Process.spawn()` installs/download helpers - -## Filesystem consolidation - -`util/filesystem.ts` is still used widely across `src/`, and raw `fs` / `fs/promises` imports still exist in multiple tooling and infrastructure files. As services and tools are effectified, they should switch from `Filesystem.*` to yielding `AppFileSystem.Service` where possible — this should happen naturally during each migration, not as a separate sweep. - -Tool-specific filesystem cleanup notes live in `tools.md`. - -## Primitives & utilities - -- [ ] `util/lock.ts` — reader-writer lock → Effect Semaphore/Permit -- [ ] `util/flock.ts` — file-based distributed lock with heartbeat → Effect.repeat + addFinalizer -- [ ] `util/process.ts` — child process spawn wrapper → return Effect instead of Promise -- [ ] `util/lazy.ts` — replace uses in Effect code with Effect.cached; keep for sync-only code - -## Destroying the facades - -This phase is no longer broadly open. There are 5 `makeRuntime(...)` call sites under `src/`, and only a small subset are still ordinary facade-removal targets. The live checklist now lives in `facades.md`. - -These facades exist because cyclic imports used to force each service to build its own independent runtime. Now that the layer DAG is acyclic and `AppRuntime` (`src/effect/app-runtime.ts`) composes everything into one `ManagedRuntime`, we're removing them. - -### Process - -For each service, the migration is roughly: - -1. **Find callers.** `grep -n "Namespace\.(methodA|methodB|...)"` across `src/` and `test/`. Skip the service file itself. -2. **Migrate production callers.** For each effectful caller that does `Effect.tryPromise(() => Namespace.method(...))`: - - Add the service to the caller's layer R type (`Layer.Layer`) - - Yield it at the top of the layer: `const ns = yield* Namespace.Service` - - Replace `Effect.tryPromise(() => Namespace.method(...))` with `yield* ns.method(...)` (or `ns.method(...).pipe(Effect.orElseSucceed(...))` for the common fallback case) - - Add `Layer.provide(Namespace.defaultLayer)` to the caller's own `defaultLayer` chain -3. **Fix tests that used the caller's raw `.layer`.** Any test that composes `Caller.layer` (not `defaultLayer`) needs to also provide the newly-required service tag. The fastest fix is usually switching to `Caller.defaultLayer` since it now pulls in the new dependency. -4. **Migrate test callers of the facade.** Tests calling `Namespace.method(...)` directly get converted to full effectful style using `testEffect(Namespace.defaultLayer)` + `it.live` / `it.effect` + `yield* svc.method(...)`. Don't wrap the test body in `Effect.promise(async () => {...})` — do the whole thing in `Effect.gen` and use `AppFileSystem.Service` / `tmpdirScoped` / `Effect.addFinalizer` for what used to be raw `fs` / `Bun.write` / `try/finally`. -5. **Delete the facades.** Once `grep` shows zero callers, remove the `export async function` block AND the `makeRuntime(...)` line from the service namespace. Also remove the now-unused `import { makeRuntime }`. - -### Pitfalls - -- **Layer caching inside tests.** `testEffect(layer)` constructs the Storage (or whatever) service once and memoizes it. If a test then tries `inner.pipe(Effect.provide(customStorage))` to swap in a differently-configured Storage, the outer cached one wins and the inner provision is a no-op. Fix: wrap the overriding layer in `Layer.fresh(...)`, which forces a new instance to be built instead of hitting the memoMap cache. This lets a single `testEffect(...)` serve both simple and per-test-customized cases. -- **`Effect.tryPromise` → `yield*` drops the Promise layer.** The old code was `Effect.tryPromise(() => Storage.read(...))` — a `tryPromise` wrapper because the facade returned a Promise. The new code is `yield* storage.read(...)` directly — the service method already returns an Effect, so no wrapper is needed. Don't reach for `Effect.promise` or `Effect.tryPromise` during migration; if you're using them on a service method call, you're doing it wrong. -- **Raw `.layer` test callers break silently in the type checker.** When you add a new R requirement to a service's `.layer`, any test that composes it raw (not `defaultLayer`) becomes under-specified. `tsgo` will flag this — the error looks like `Type 'Storage.Service' is not assignable to type '... | Service | TestConsole'`. Usually the fix is to switch that composition to `defaultLayer`, or add `Layer.provide(NewDep.defaultLayer)` to the custom composition. -- **Tests that do async setup with `fs`, `Bun.write`, `tmpdir`.** Convert these to `AppFileSystem.Service` calls inside `Effect.gen`, and use `tmpdirScoped()` instead of `tmpdir()` so cleanup happens via the scope finalizer. For file operations on the actual filesystem (not via a service), a small helper like `const writeJson = Effect.fnUntraced(function* (file, value) { const fs = yield* AppFileSystem.Service; yield* fs.makeDirectory(path.dirname(file), { recursive: true }); yield* fs.writeFileString(file, JSON.stringify(value, null, 2)) })` keeps the migration tests clean. - -### Migration log - -- `SessionStatus` — migrated 2026-04-11. Replaced the last route and retry-policy callers with `AppRuntime.runPromise(SessionStatus.Service.use(...))` and removed the `makeRuntime(...)` facade. -- `ShareNext` — migrated 2026-04-11. Swapped remaining async callers to `AppRuntime.runPromise(ShareNext.Service.use(...))`, removed the `makeRuntime(...)` facade, and kept instance bootstrap on the shared app runtime. -- `SessionTodo` — migrated 2026-04-10. Already matched the target service shape in `session/todo.ts`: single namespace, traced Effect methods, and no `makeRuntime(...)` facade remained; checklist updated to reflect the completed migration. -- `Storage` — migrated 2026-04-10. One production caller (`Session.diff`) and all storage.test.ts tests converted to effectful style. Facades and `makeRuntime` removed. -- `SessionRunState` — migrated 2026-04-11. Single caller in `server/routes/instance/session.ts` converted; facade removed. -- `Account` — migrated 2026-04-11. Callers in `server/routes/instance/experimental.ts` and `cli/cmd/account.ts` converted; facade removed. -- `Instruction` — migrated 2026-04-11. Test-only callers converted; facade removed. -- `FileWatcher` — migrated 2026-04-11. Callers in `project/bootstrap.ts` and test converted; facade removed. -- `Question` — migrated 2026-04-11. Callers in `server/routes/instance/question.ts` and test converted; facade removed. -- `Truncate` — migrated 2026-04-11. Caller in `tool/tool.ts` and test converted; facade removed. - -## Route handler effectification - -Route-handler migration guidance and checklist live in `routes.md`. +- [ ] The code has a single Effect body instead of Promise wrappers around + service calls. +- [ ] Expected failures are typed errors, not thrown exceptions or defects. +- [ ] Layer requirements are explicit. +- [ ] Tests use Effect-aware fixtures and focused layers. +- [ ] Public behavior and wire shapes are preserved unless intentionally + changed. diff --git a/packages/opencode/specs/effect/routes.md b/packages/opencode/specs/effect/routes.md index 8066bda34..7dcd80ce9 100644 --- a/packages/opencode/specs/effect/routes.md +++ b/packages/opencode/specs/effect/routes.md @@ -1,57 +1,61 @@ -# Route handler effectification +# HTTP Route Patterns -Practical reference for converting server route handlers in `packages/opencode` to a single `AppRuntime.runPromise(Effect.gen(...))` body. +Current guidance for `packages/opencode/src/server/routes/instance/httpapi`. -## Goal +## Handler Shape -Route handlers should wrap their entire body in a single `AppRuntime.runPromise(Effect.gen(...))` call, yielding services from context rather than calling facades one-by-one. - -This eliminates multiple `runPromise` round-trips and lets handlers compose naturally. +Use `HttpApiBuilder.group(...)` for normal JSON and streaming HTTP API +endpoints. Yield stable services once while building the handler layer, +then close over those services in endpoint implementations. ```ts -// Before - one facade call per service -;async (c) => { - await SessionRunState.assertNotBusy(id) - await Session.removeMessage({ sessionID: id, messageID }) - return c.json(true) -} - -// After - one Effect.gen, yield services from context -;async (c) => { - await AppRuntime.runPromise( - Effect.gen(function* () { - const state = yield* SessionRunState.Service - const session = yield* Session.Service - yield* state.assertNotBusy(id) - yield* session.removeMessage({ sessionID: id, messageID }) - }), - ) - return c.json(true) -} +export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", (handlers) => + Effect.gen(function* () { + const session = yield* Session.Service + + return handlers.handle("list", () => session.list()) + }), +) ``` -## Rules +Use raw `HttpRouter` only for routes that do not fit the request/response +HttpApi model, such as WebSocket upgrades or catch-all fallback routes. + +Do not rebuild stable layers inside request handlers. Provide stable +services at the route/layer boundary and use request-level provisioning +only for request-derived context. + +## Error Boundaries + +Expected service errors should be mapped at the handler boundary to +endpoint-declared public HTTP errors. Keep one-off mappings inline. Extract +small helpers when the same mapping repeats. + +Generic middleware should not become a domain-error mapper. It should +handle cross-cutting concerns and final unknown-defect fallback. + +Public JSON errors should be explicit schema contracts declared on each +endpoint or group. Built-in `HttpApiError.*` is fine only when its generated +body is intentionally the public wire shape. -- Wrap the whole handler body in one `AppRuntime.runPromise(Effect.gen(...))` call when the handler is service-heavy. -- Yield services from context instead of calling async facades repeatedly. -- When independent service calls can run in parallel, use `Effect.all(..., { concurrency: "unbounded" })`. -- Prefer one composed Effect body over multiple separate `runPromise(...)` calls in the same handler. +Preserve existing `{ name, data }` error bodies until a deliberate breaking +API change. -## Current route files +## OpenAPI Compatibility -Current instance route files live under `src/server/routes/instance/httpapi`. -Most handlers already yield stable services at route-layer construction and then -close over those services in endpoint implementations. +`public.ts` still owns SDK/OpenAPI compatibility transforms. Shrink those +transforms by tightening source schemas one workaround at a time. -Files still worth tracking here: +When an OpenAPI-visible source schema changes: -- [ ] `handlers/session.ts` — still the heaviest mixed file; some paths keep compatibility translations and direct event publication -- [ ] `handlers/experimental.ts` — mixed state; some handlers still rely on request-local context reads -- [ ] `middleware/*` — still contains compatibility policy for auth, compression, errors, instance context, and workspace routing -- [ ] `public.ts` — still owns SDK/OpenAPI compatibility translation shims -- [ ] raw route modules — WebSocket and catch-all routes should stay explicit and avoid rebuilding stable layers per request +- verify the generated SDK diff is intentional +- preserve legacy compatibility unless the PR explicitly changes it +- prefer source-schema fixes over new post-processing rules -## Notes +## Checklist For Route PRs -- Route conversion is now less about backend migration and more about removing the remaining direct `Instance.*` reads, request-local service plumbing, and OpenAPI compatibility shims. -- Prefer route-layer service capture over rebuilding or providing stable layers inside individual handlers. +- [ ] Stable services are yielded at handler-layer construction. +- [ ] Expected domain errors are translated at the route boundary. +- [ ] Endpoint/group error schemas describe the public body and status. +- [ ] Middleware does not gain new domain-specific name checks. +- [ ] Raw routes are used only when HttpApi is the wrong abstraction. diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 1fc6a4478..12ac26704 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -1,22 +1,15 @@ -# Schema migration +# Schema Migration -Practical reference for migrating data types in `packages/opencode` from -Zod-first definitions to Effect Schema. +Use Effect Schema as the source of truth for domain models, DTOs, IDs, +inputs, outputs, and typed errors. -## Goal +This is guidance, not an inventory. Do not use this file to track which +schema modules are complete; verify current state with `git grep` before +starting a migration. -Use Effect Schema as the source of truth for domain models, IDs, inputs, -outputs, and typed errors. Prefer native Effect Schema, Standard Schema, and -native JSON Schema generation at HTTP, tool, and AI SDK boundaries. +## Preferred Shapes -The long-term driver is `specs/effect/http-api.md`: Schema-first DTOs should -flow through `HttpApi` / `HttpRouter` without a Zod translation layer. - -## Preferred shapes - -### Data objects - -Use `Schema.Class` for structured data. +Use `Schema.Class` for exported data objects with a clear domain identity: ```ts export class Info extends Schema.Class("Foo.Info")({ @@ -26,18 +19,16 @@ export class Info extends Schema.Class("Foo.Info")({ }) {} ``` -If a schema needs local static helpers, use the two-step `withStatics` pattern: +Use `Schema.Struct` for local shapes and simple nested objects: ```ts -export const Info = Schema.Struct({ +const Payload = Schema.Struct({ id: FooID, - name: Schema.String, -}).pipe(withStatics((s) => ({ decode: Schema.decodeUnknownOption(s) }))) + value: Schema.String, +}) ``` -### Errors - -Use `Schema.TaggedErrorClass` for domain errors. +Use `Schema.TaggedErrorClass` for expected domain errors: ```ts export class NotFoundError extends Schema.TaggedErrorClass()("FooNotFoundError", { @@ -45,309 +36,53 @@ export class NotFoundError extends Schema.TaggedErrorClass()("Foo }) {} ``` -### IDs and branded leaf types - -Keep branded/schema-backed IDs as Effect schemas. - -### Refinements - -Reuse named refinements instead of re-spelling numeric or string constraints in -every schema. Boundary JSON Schema helpers should normalize native Effect JSON -Schema output only where a provider requires it. - -```ts -const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) -const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) -const HexColor = Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)) -``` - -## Compatibility rule - -During migration, route validators, tool parameters, and AI SDK schemas should -consume Effect schemas directly or use a narrow boundary helper. Avoid -maintaining a second hand-written Zod schema. - -The default should be: - -- Effect Schema owns the type -- new domain models should not start Zod-first unless there is a concrete - boundary-specific need - -## When Zod can stay - -It is fine to keep a Zod-native schema temporarily when: - -- the type is only used at an HTTP or tool boundary and is not reused elsewhere -- the validator is part of an existing public API that explicitly accepts Zod -- the migration would force unrelated churn across a large call graph - -When this happens, prefer leaving a short note or TODO rather than silently -creating a parallel schema source of truth. - -## Boundary helpers - -Use narrow helpers at concrete boundaries instead of a generic Schema → Zod bridge. - -- Tool parameters: `ToolJsonSchema.fromSchema(...)` and `ToolJsonSchema.fromTool(...)` -- Public config/TUI schemas: `packages/opencode/script/schema.ts` -- AI SDK object generation: `Schema.toStandardSchemaV1(...)` plus `Schema.toStandardJSONSchemaV1(...)` - -Plugin tools are the main remaining intentional Zod boundary because the public -plugin API exposes `tool.schema = z` and `args: z.ZodRawShape`. - -### Local `DeepMutable` in `config/config.ts` - -`Schema.Struct` produces `readonly` types. Some consumer code (notably the -`Config` service) mutates `Info` objects directly, so a readonly-stripping -utility is needed when casting the derived zod schema's output type. - -`Types.DeepMutable` from effect-smol would be a drop-in, but it widens -`unknown` to `{}` in the fallback branch — a bug that affects any schema -using `Schema.Record(String, Schema.Unknown)`. - -Tracked upstream as `effect:core/x228my`: "Types.DeepMutable widens unknown -to `{}`." Once that lands, the local `DeepMutable` copy can be deleted and -`Types.DeepMutable` used directly. +Use branded schema-backed IDs for single-value domain identifiers. -## Ordering +## Boundary Rule -Migrate in this order: +Effect Schema should own the type. Boundaries should consume Effect Schema +directly or use narrow boundary-specific helpers. Avoid reintroducing a +generic Effect Schema -> Zod bridge. -1. Shared leaf models and `schema.ts` files -2. Exported `Info`, `Input`, `Output`, and DTO types -3. Tagged domain errors -4. Service-local internal models -5. Route and tool boundary validators that can switch to native Effect Schema helpers +Current intentional boundaries: -This keeps shared types canonical first and makes boundary updates mostly -mechanical. +- Public plugin tools still expose Zod through `tool.schema = z`. +- Tool parameters use tool-specific JSON Schema helpers. +- Public config and TUI schema generation goes through the schema script. +- AI SDK object generation uses Standard Schema / JSON Schema helpers. -## Progress tracker +When Zod must stay temporarily, leave a short note explaining the boundary +or compatibility reason. -### `src/config/` ✅ complete +## Refinements -All of `packages/opencode/src/config/` has been migrated. The `export const -` values are all Effect Schema at source. +Reuse named refinements instead of re-spelling constraints: -A file is considered "done" when: - -- its exported schema values (`Info`, `Input`, `Event`, `Definition`, etc.) - are authored as Effect Schema -- any remaining Zod is an explicit boundary compatibility choice, not a - hand-written parallel source of truth - -Files that meet this bar but still carry a compatibility boundary are checked -off with an inline note describing the boundary and what unblocks its removal. - -- [x] skills, formatter, console-state, mcp, lsp, permission (leaves), model-id, command, plugin, provider -- [x] server, layout -- [x] keybinds -- [x] permission#Info -- [x] agent -- [x] config.ts root - -### `src/*/schema.ts` leaf modules - -These are the highest-priority next targets. Each is a small, self-contained -schema module with a clear domain. - -- [x] `src/account/schema.ts` -- [x] `src/control-plane/schema.ts` -- [x] `src/permission/schema.ts` -- [x] `src/project/schema.ts` -- [x] `src/provider/schema.ts` -- [x] `src/pty/schema.ts` -- [x] `src/question/schema.ts` -- [x] `src/session/schema.ts` -- [x] `src/storage/schema.ts` -- [x] `src/sync/schema.ts` -- [x] `src/tool/schema.ts` -- [x] `src/util/schema.ts` - -### Session domain - -Major cluster. Message + event types flow through the SSE API and every SDK -output, so byte-identical SDK surface is critical. - -Suggested order for this cluster, starting from the leaves that `session.ts` -and the SSE/event surface depend on: - -1. `src/session/schema.ts` ✅ already migrated -2. `src/provider/schema.ts` if `message-v2.ts` still relies on zod-first IDs -3. `src/lsp/*` schema leaves needed by `LSP.Range` -4. `src/snapshot/*` leaves used by `Snapshot.FileDiff` -5. `src/session/message-v2.ts` -6. `src/session/message.ts` -7. `src/session/prompt.ts` -8. `src/session/revert.ts` -9. `src/session/summary.ts` -10. `src/session/status.ts` -11. `src/session/todo.ts` -12. `src/session/session.ts` -13. `src/session/compaction.ts` - -Dependency sketch: - -```text -session.ts -|- project/schema.ts -|- control-plane/schema.ts -|- permission/schema.ts -|- snapshot/* -|- message-v2.ts -| |- provider/schema.ts -| |- lsp/* -| |- snapshot/* -| |- sync/index.ts -| `- bus/bus-event.ts -|- sync/index.ts -|- bus/bus-event.ts -`- util/update-schema.ts +```ts +const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) +const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) ``` -Working rule for this cluster: - -- migrate reusable leaf schemas and nested payload objects first -- migrate aggregate DTOs like `Session.Info` after their nested pieces exist as - named Schema values -- leave zod-only event/update helpers in place temporarily when converting - them would force unrelated churn across sync/bus boundaries - -`message-v2.ts` first-pass outline: - -1. Schema-backed imports already available - - `SessionID`, `MessageID`, `PartID` - - `ProviderID`, `ModelID` -2. Local leaf objects to extract and migrate first - - output format payloads - - common part bases like `PartBase` - - timestamp/range helper objects like `time.start/end` - - file/source helper objects - - token/cost/model helper objects -3. Part variants built from those leaves - - `SnapshotPart`, `PatchPart`, `TextPart`, `ReasoningPart` - - `FilePart`, `AgentPart`, `CompactionPart`, `SubtaskPart` - - retry/step/tool related parts -4. Higher-level unions and DTOs - - `FilePartSource` - - part unions - - message unions and assistant/user payloads -5. Errors and event payloads last - - `NamedError.create(...)` shapes can stay temporarily if converting them to - `Schema.TaggedErrorClass` would force unrelated churn - - `SyncEvent.define(...)` and `BusEvent.define(...)` payloads can use - derived `.zod` at remaining zod-based HTTP/OpenAPI boundaries - -Possible later tightening after the Schema-first migration is stable: - -- promote repeated opaque strings and timestamp numbers into branded/newtype - leaf schemas where that adds domain value without changing the wire format - -- [x] `src/session/compaction.ts` -- [x] `src/session/message-v2.ts` -- [x] `src/session/message.ts` -- [x] `src/session/prompt.ts` -- [x] `src/session/revert.ts` -- [x] `src/session/session.ts` -- [x] `src/session/status.ts` -- [x] `src/session/summary.ts` -- [x] `src/session/todo.ts` - -### Provider domain - -- [x] `src/provider/auth.ts` -- [x] `src/provider/models.ts` -- [x] `src/provider/provider.ts` - -### Tool schemas - -Each tool declares its parameters via a zod schema. Tools are consumed by -both the in-process runtime and the AI SDK's tool-calling layer, so the -emitted JSON Schema must stay byte-identical. - -- [x] `src/tool/apply_patch.ts` -- [x] `src/tool/bash.ts` -- [x] `src/tool/edit.ts` -- [x] `src/tool/glob.ts` -- [x] `src/tool/grep.ts` -- [x] `src/tool/invalid.ts` -- [x] `src/tool/lsp.ts` -- [x] `src/tool/plan.ts` -- [x] `src/tool/question.ts` -- [x] `src/tool/read.ts` -- [x] `src/tool/registry.ts` -- [x] `src/tool/skill.ts` -- [x] `src/tool/task.ts` -- [x] `src/tool/todo.ts` -- [x] `src/tool/tool.ts` -- [x] `src/tool/webfetch.ts` -- [x] `src/tool/websearch.ts` -- [x] `src/tool/write.ts` - -### HTTP route boundaries - -The server route tree now lives under `src/server/routes/instance/httpapi` and -uses Effect HttpApi contracts for request and response schemas. Remaining schema -work is no longer a Hono route migration; it is compatibility cleanup around -derived `.zod` statics, OpenAPI translation shims, and route groups that still -need explicit SDK-visible error contracts. - -Good follow-up targets: +Prefer domain-named leaf schemas when the name improves callers or error +messages. Avoid adding brands purely for novelty. -- shrink `public.ts` legacy OpenAPI translation shims one SDK-compatible slice at a time -- replace production `.zod.safeParse(...)` call sites with Effect Schema decoders -- remove derived `.zod` statics after their production consumers are gone -- declare route-group errors directly instead of relying on compatibility middleware +## Migration Order -### Everything else +For a domain that still has mixed schemas: -Small / shared / control-plane / CLI. Mostly independent; can be done -piecewise. +1. Shared leaf models and branded IDs. +2. Exported `Info`, `Input`, `Output`, and event payload types. +3. Expected domain errors. +4. Service-local internal models. +5. HTTP/tool/AI boundary validators. -- [ ] `src/acp/agent.ts` -- [ ] `src/agent/agent.ts` -- [x] `src/bus/bus-event.ts` -- [ ] `src/bus/index.ts` -- [ ] `src/cli/cmd/tui/config/tui-migrate.ts` -- [ ] `src/cli/cmd/tui/config/tui-schema.ts` -- [ ] `src/cli/cmd/tui/config/tui.ts` -- [ ] `src/cli/cmd/tui/event.ts` -- [ ] `src/cli/ui.ts` -- [ ] `src/command/index.ts` -- [x] `src/control-plane/adapters/worktree.ts` -- [x] `src/control-plane/types.ts` -- [x] `src/control-plane/workspace.ts` -- [ ] `src/file/index.ts` -- [ ] `src/file/ripgrep.ts` -- [ ] `src/file/watcher.ts` -- [ ] `src/format/index.ts` -- [ ] `src/id/id.ts` -- [ ] `src/ide/index.ts` -- [ ] `src/installation/index.ts` -- [ ] `src/lsp/client.ts` -- [ ] `src/lsp/lsp.ts` -- [ ] `src/mcp/auth.ts` -- [ ] `src/patch/index.ts` -- [ ] `src/plugin/github-copilot/models.ts` -- [ ] `src/project/project.ts` -- [ ] `src/project/vcs.ts` -- [ ] `src/pty/index.ts` -- [ ] `src/skill/index.ts` -- [ ] `src/snapshot/index.ts` -- [ ] `src/storage/db.ts` -- [ ] `src/storage/storage.ts` -- [x] `src/sync/index.ts` — public API (`SyncEvent.define`) is Schema-first; `payloads()` still derives zod for the remaining HTTP/OpenAPI boundary -- [ ] `src/util/fn.ts` -- [ ] `src/util/log.ts` -- [ ] `src/util/update-schema.ts` -- [ ] `src/worktree/index.ts` +Keep public wire shapes stable unless the PR is explicitly a breaking API +change. -## Notes +## Checklist For A PR -- Prefer one canonical schema definition. Avoid maintaining parallel Zod and - Effect definitions for the same domain type. -- Keep the migration incremental. Converting the domain model first is more - valuable than converting every boundary in the same change. -- Every migrated file should leave the generated SDK output (`packages/sdk/ -openapi.json` and `packages/sdk/js/src/v2/gen/types.gen.ts`) byte-identical - unless the change is deliberately user-visible. +- [ ] There is one schema source of truth for each migrated type. +- [ ] Remaining Zod is an intentional boundary choice. +- [ ] Public JSON/OpenAPI output is unchanged or intentionally updated. +- [ ] Derived helpers are narrow and boundary-specific. +- [ ] Tests assert behavior, not duplicated schema implementation details. diff --git a/packages/opencode/specs/effect/todo.md b/packages/opencode/specs/effect/todo.md new file mode 100644 index 000000000..9261811c3 --- /dev/null +++ b/packages/opencode/specs/effect/todo.md @@ -0,0 +1,237 @@ +# Effect TODO + +Short roadmap for Effect cleanup in `packages/opencode`. + +Current patterns and examples live in [`guide.md`](./guide.md). Test +migration rules live in +[`test/EFFECT_TEST_MIGRATION.md`](../../test/EFFECT_TEST_MIGRATION.md). +Older deep-dive notes in this directory may still be useful, but treat +this roadmap and the guide as the current entry points. + +This is a planning map, not a verified inventory. Before starting a task, +re-run a targeted `git grep` from current `dev` and update this file if +the inventory changed. + +## Priorities + +```text +P0 ERR + RENDER + HTTP + Make expected failures typed, render them well, and stop relying on + generic HTTP error guesswork. + +P1 TEST + Convert touched tests to the ideal Effect test patterns from the guide. + +P2 RF + Move mutable runtime flags into typed runtime/config services. + +P3 GLOBAL + Make global paths explicit and remove import-time side effects. + +P4 INST + BRIDGE + Remove ambient Instance coupling while keeping Promise/callback interop. + +P5 PROC + FS + Replace raw process/filesystem edges with typed Effect services. + +P6 OA + Shrink OpenAPI compatibility shims as source schemas improve. +``` + +## Work Paths + +- `ERR` Typed errors — replace legacy `NamedError.create(...)` and + `Effect.die(...)` for expected service failures with + `Schema.TaggedErrorClass` errors on the Effect error channel. + Shrinks: [`NamedError`](../../../core/src/util/error.ts) usage. +- `RENDER` User-visible error rendering — preserve structured typed-error + details at CLI, HTTP, and tool boundaries. + Shrinks: opaque `Error: Name` rendering. +- `HTTP` HTTP route cleanup — make route errors explicit instead of + relying on generic middleware to guess status/body from error names. + Shrinks: [`middleware/error.ts`](../../src/server/routes/instance/httpapi/middleware/error.ts) + and route-level compatibility shims. +- `TEST` Effect test migration — use `testEffect`, `it.live`, and + `it.instance` with explicit layers. + Shrinks: Promise-style tests, sleeps, mutable global test flags. +- `RF` RuntimeFlags / Flag deletion — move mutable + [`Flag`](../../../core/src/flag/flag.ts) reads into typed runtime/config + services. + Shrinks: [`flag.ts`](../../../core/src/flag/flag.ts), + [`test/fixture/flag.ts`](../../test/fixture/flag.ts). +- `GLOBAL` Global paths / import side effects — make global path state + explicit and testable instead of mutable module state. + Shrinks: [`global.ts`](../../../core/src/global.ts) import-time side + effects, mutable `Global.Path` overrides, and its `Flag` dependency. +- `INST` Instance shim — remove ambient `Instance` usage and old ALS + access patterns. + Shrinks: [`src/project/instance.ts`](../../src/project/instance.ts). +- `BRIDGE` Promise/callback interop — keep bridge helpers, but reduce + legacy ALS coupling. + Shrinks: [`src/effect/bridge.ts`](../../src/effect/bridge.ts) + dependency on [`project/instance.ts`](../../src/project/instance.ts). +- `PROC` AppProcess migration — prefer `AppProcess.Service` over raw + process wrappers. + Shrinks: direct spawn callsites and legacy process helpers. +- `FS` AppFileSystem migration — prefer `AppFileSystem.Service` over raw + filesystem APIs. + Shrinks: direct `fs` / `Bun.file` service callsites where inappropriate. +- `RT` Runtime/facade cleanup — remove service-local `makeRuntime` + facades when not intentional. + Shrinks: async facade exports around services and + [`run-service.ts`](../../src/effect/run-service.ts) usage. +- `OA` OpenAPI compatibility — tighten source schemas instead of + post-processing generated OpenAPI. + Shrinks: schema workaround blocks in + [`public.ts`](../../src/server/routes/instance/httpapi/public.ts). + +## P0: Errors, Rendering, And HTTP + +This should be the next big cleanup theme. The codebase is moving toward +typed Effect failures, but the user-facing boundaries still leak old +shapes and sometimes collapse rich errors into opaque strings. + +### Problems + +- Some expected service failures still use `NamedError.create(...)`. +- Some expected service failures still become `Effect.die(...)`, which + makes them defects instead of typed, recoverable failures. +- CLI and HTTP boundaries can render structured errors as generic + `Error: SomeName` output. +- HTTP error middleware still guesses status codes from error names like + `Worktree*` or `ProviderAuthValidationFailed`. +- Route handlers and route groups do not consistently declare the public + error body they intend to expose. +- Repeated route error translations do not yet have a clear home: some + should stay inline, some deserve tiny shared mapper helpers. +- Unknown 500s should log full detail server-side while returning a safe + public body. + +### Target Shape + +- Services define expected failures with `Schema.TaggedErrorClass`. +- Services export an `Error` union and include it in method return types. +- Expected failures stay on the Effect error channel. +- `Effect.die(...)` is reserved for defects: bugs, impossible states, + violated invariants, or final unknown-boundary fallbacks. +- Inside `Effect.gen` / `Effect.fn`, use `yield* new MyError(...)` for + direct expected failures. +- Domain services do not import HTTP status codes, `HttpApiError`, or + route-specific error schemas. +- HTTP route groups make their public error contracts obvious. +- Handlers map service errors to declared HTTP errors at the boundary. +- Shared mapper helpers are only for repeated translations, not a giant + central registry of every domain error. +- Generic HTTP middleware should shrink; it should not accumulate more + name-based domain knowledge. + +### First PR Candidates + +- [ ] `RENDER-1` Fix CLI top-level rendering for typed config errors. +- [ ] `ERR-1` Convert [`storage/storage.ts`](../../src/storage/storage.ts) + not-found errors. +- [ ] `ERR-2` Convert [`worktree/index.ts`](../../src/worktree/index.ts) + errors and remove matching HTTP name checks where possible. +- [ ] `ERR-3` Convert [`provider/auth.ts`](../../src/provider/auth.ts) + validation errors. +- [ ] `HTTP-1` Remove the unknown-500 stack leak from + [`middleware/error.ts`](../../src/server/routes/instance/httpapi/middleware/error.ts). +- [ ] `HTTP-2` Audit one route group for explicit error contracts and + decide which mappings stay inline vs. shared helper. + +## P1: Tests + +When touching tests, migrate them toward the ideal patterns in +[`test/EFFECT_TEST_MIGRATION.md`](../../test/EFFECT_TEST_MIGRATION.md): + +- Use `testEffect(...)` with explicit layers. +- Prefer `it.instance(...)` for service tests that need an instance. +- Prefer `it.live(...)` for real timers, filesystem mtimes, child + processes, git, locks, or other live integration behavior. +- Avoid sleeps; wait on real events or deterministic state transitions. +- Do not mutate `process.env` or mutable globals after layers are built. +- Use explicit layer variants, such as `RuntimeFlags.layer(...)`, for + behavior changes. + +## P2: RuntimeFlags / Flag Deletion + +Recently completed: + +- [x] Plugin/pure-mode flags moved to RuntimeFlags. +- [x] Tool visibility flags moved to RuntimeFlags. +- [x] Built-in websearch provider selection uses the same runtime flags as + tool visibility. +- [x] Removed global default-plugin disabling from test preload. + +Recommended next PRs: + +```text +RF-1 scout consumers ─┐ + ├─ can run in parallel +RF-2 plan-mode prompt ┘ + └─ RF-3 event-system cluster, stacked only if RF-2 still touches prompt.ts + +RF-4 workspaces cluster: later, after mutable Flag tests are cleaned up +``` + +- [ ] `RF-1` Move scout reads in [`agent.ts`](../../src/agent/agent.ts) + and [`reference.ts`](../../src/reference/reference.ts). +- [ ] `RF-2` Move plan-mode prompt read in + [`session/prompt.ts`](../../src/session/prompt.ts). +- [ ] `RF-3` Move event-system reads in session prompt/processor/ + compaction and TUI debug plugin. +- [ ] `RF-4` Move workspaces reads in session/sync/control-plane after + tests stop relying on mutable `Flag` timing. +- [ ] Delete [`test/fixture/flag.ts`](../../test/fixture/flag.ts) once + tests no longer mutate `Flag`. +- [ ] Delete [`flag.ts`](../../../core/src/flag/flag.ts) once no packages + import it. + +## P3: Global Paths + +[`global.ts`](../../../core/src/global.ts) is real connective tissue, not +just cosmetic ugliness. It currently mixes path calculation, import-time +directory creation, `Flock` setup, mutable exported `Path` state, and a +`Flag` dependency. + +Problems to reduce: + +- Importing the module creates directories. +- Tests override `Global.Path` by mutating exported module state. +- Most callers use `Global.Path` directly instead of the Effect service. +- `Global.make()` still reads mutable `Flag.OPENCODE_CONFIG_DIR`. + +Next PR candidates: + +- [ ] Replace mutable `Global.Path` test overrides with explicit test + layers or scoped helpers. +- [ ] Move directory creation and `Flock` setup behind an explicit init + boundary where possible. +- [ ] Remove the `Flag` dependency from global path resolution. + +## P4: Instance And Bridge + +[`project/instance.ts`](../../src/project/instance.ts) is the deletion +target. [`effect/bridge.ts`](../../src/effect/bridge.ts) is not a near-term +deletion target; Promise/callback interop will continue to exist. + +Goal: + +- Keep a sanctioned bridge for Promise/callback boundaries. +- Reduce bridge dependence on legacy `Instance.restore` / `Instance.current`. +- Move callers toward `InstanceRef`, `WorkspaceRef`, `InstanceState`, or + explicit context where practical. +- Delete `project/instance.ts` only after ambient Instance coupling is gone. + +## Lower Priority Tracks + +- `PROC` / `FS` — continue AppProcess and AppFileSystem migrations as + focused PRs when touching relevant files. +- `RT` — remove service-local runtime facades only when they are not an + intentional boundary. +- `OA` — shrink [`public.ts`](../../src/server/routes/instance/httpapi/public.ts) + by tightening source schemas one workaround at a time. +- `fetch` → `HttpClient` — migrate raw fetch callsites when the caller is + already effectful or being effectified. +- `Tools` — remaining tool cleanup is narrow: `webfetch` HTML extraction + and `shell` raw stream/promise edges. diff --git a/packages/opencode/test/EFFECT_TEST_MIGRATION.md b/packages/opencode/test/EFFECT_TEST_MIGRATION.md index 2c160b993..73acea914 100644 --- a/packages/opencode/test/EFFECT_TEST_MIGRATION.md +++ b/packages/opencode/test/EFFECT_TEST_MIGRATION.md @@ -1,16 +1,21 @@ -# Effect Test Migration Plan +# Effect Test Migration -This document describes how to move opencode tests out of Promise-land and into the shared `testEffect` pattern. +Move tests that exercise Effect services out of Promise-land and into the +shared `testEffect` pattern. + +This file is guidance, not a live inventory. Before claiming a migration, +search current `dev` for the exact anti-pattern and update any PR notes +with what you actually changed. ## Target Pattern -Every test file that exercises Effect services should have one local runner near the top: +Every Effect service test should have one local runner near the top: ```ts const it = testEffect(layer) ``` -Then each test should use one of the runner methods: +Use the runner method that matches the behavior: ```ts it.effect("pure service behavior", () => @@ -23,7 +28,7 @@ it.effect("pure service behavior", () => it.instance("instance-local behavior", () => Effect.gen(function* () { const test = yield* TestInstance - // test.directory is a scoped temp opencode instance + expect(test.directory).toContain("opencode-test-") }), ) @@ -35,40 +40,22 @@ it.live("live filesystem or process behavior", () => ) ``` -Use `it.effect` for pure Effect code that should run with `TestClock` and `TestConsole`. -Use `it.instance` when the test needs one scoped opencode instance. -Use `it.live` when the test depends on real time, filesystem mtimes, git, child processes, servers, file watchers, or OS behavior. - -## Anti-Patterns To Remove - -Avoid these in tests that already target Effect services: +## Choosing The Runner -- `test(..., async () => Effect.runPromise(...))` -- local `run(...)`, `load(...)`, `svc(...)`, or `runtime.runPromise(...)` wrappers that only provide a layer -- `tmpdir()` plus `WithInstance.provide(...)` in Promise test bodies -- custom `ManagedRuntime.make(...)` in test files -- Promise `try/catch` around Effect failures -- `Promise.withResolvers`, `Bun.sleep`, or `setTimeout` for synchronization when `Deferred`, `Fiber`, or `Effect.sleep` can express the same behavior +- `it.effect(...)` — pure Effect behavior with `TestClock` and + `TestConsole`. +- `it.instance(...)` — service behavior that needs one scoped opencode + instance. +- `it.live(...)` — real time, filesystem mtimes, child processes, git, + locks, servers, watchers, or OS behavior. -Promise helpers are acceptable at the boundary for non-Effect APIs, but they should be yielded from an Effect body with `Effect.promise(...)` rather than becoming the test harness. +Most integration-style tests use `it.live(...)` or `it.instance(...)`. ## Layer Rules -Compose tests from open service layers, not closed `defaultLayer` graphs when a dependency needs replacing. - -Good: - -```ts -const layer = Config.layer.pipe( - Layer.provide(AppFileSystem.defaultLayer), - Layer.provide(Env.defaultLayer), - Layer.provide(AuthTest.empty), - Layer.provide(AccountTest.empty), - Layer.provide(NpmTest.noop), -) -``` - -Avoid using a fully closed layer and hoping to override an inner dependency later. Once `Agent.defaultLayer` has already provided `Config.defaultLayer`, tests cannot cleanly swap the `Npm.Service` used by that config layer. +Compose tests from open service layers when a dependency needs replacing. +Do not use a closed `defaultLayer` and then try to override an inner +dependency after it has already been provided. Prefer small reusable fake boundary layers in `test/fake/*`: @@ -80,146 +67,103 @@ SkillTest.empty ProviderTest.fake().layer ``` -Do not add generic test-layer builders until repeated local compositions prove the need. Shared fake boundary services are the first reusable unit. Pre-composed subtrees such as `AgentTest.withPlugins` should come later, only after the same graph appears in multiple files. +Use `Layer.mock` for partial service stubs. Missing methods should fail +loudly if the test accidentally calls them. + +Do not add generic test-layer builders until repeated local compositions +prove the need. ## Fixture Rules Use Effect-aware fixtures from `test/fixture/fixture.ts`: -- `TestInstance` inside `it.instance(...)` for the current temp instance path -- `tmpdirScoped(...)` inside `Effect.gen` for additional temp directories -- `provideInstance(dir)(effect)` when one test needs to switch instance context -- `provideTmpdirInstance((dir) => effect, options)` when a live test needs custom instance setup or multiple instance scopes -- `disposeAllInstances()` in `afterEach` only for integration tests that intentionally touch shared instance registries +- `TestInstance` inside `it.instance(...)` for the current temp instance. +- `tmpdirScoped(...)` inside `Effect.gen` for extra temp directories. +- `provideInstance(dir)(effect)` when one test needs to switch instance + context. +- `provideTmpdirInstance((dir) => effect, options)` when a live test needs + custom instance setup or multiple instance scopes. +- `disposeAllInstances()` in `afterEach` only for integration tests that + intentionally touch shared instance registries. -Use finalizers only as a temporary bridge for existing global mutations: +Avoid mutable global setup. If a global mutation is unavoidable during a +migration, scope it with acquire/release and treat it as temporary. -```ts -yield * - Effect.acquireUseRelease( - Effect.sync(() => { - const previous = process.env.MY_FLAG - process.env.MY_FLAG = "1" - return previous - }), - () => testBody, - (previous) => - Effect.sync(() => { - if (previous === undefined) delete process.env.MY_FLAG - else process.env.MY_FLAG = previous - }), - ) -``` +Long term, tests should not toggle `process.env`, `Global.Path`, or +mutable flags when behavior can be modeled with services. Prefer layers +such as `RuntimeFlags.layer(...)` or focused fake services. -TODO: eliminate this pattern over time. Tests should not toggle process-global flags or env vars when the behavior can be modeled with services. Prefer moving flag/env reads behind injectable services such as `Config.Service`, `Env.Service`, or focused test layers, then provide the desired test value through the layer graph instead of mutating `process.env` or `Global.Path`. +## Anti-Patterns To Remove -## Conversion Recipe +- `test(..., async () => Effect.runPromise(...))` +- local `run(...)`, `load(...)`, `svc(...)`, or `runtime.runPromise(...)` + wrappers that only provide a layer +- `tmpdir()` plus legacy instance provision in Promise test bodies +- custom `ManagedRuntime.make(...)` in test files +- Promise `try/catch` around Effect failures +- `Promise.withResolvers`, `Bun.sleep`, or `setTimeout` for synchronization + when events, `Deferred`, fibers, or deterministic state checks fit +- mutable env/global/flag changes after layers are built -1. Identify the real service under test and its open `*.layer`. -2. Build one top-level `layer` with real dependencies where they are relevant and `test/fake/*` layers at slow or external boundaries. -3. Replace local Promise wrappers with Effect helpers: +Promise helpers are acceptable at non-Effect boundaries, but yield them from +inside an Effect body with `Effect.promise(...)` rather than making them the +test harness. -```ts -const run = Effect.fn("MyTest.run")(function* (input: Input) { - const service = yield* MyService.Service - return yield* service.run(input) -}) -``` +## Conversion Recipe -4. Convert `test(..., async () => { ... })` to `it.effect`, `it.instance`, or `it.live`. +1. Identify the real service under test and whether its open `layer` or + closed `defaultLayer` is appropriate. +2. Build one top-level `layer` with real dependencies where relevant and + fake layers at slow or external boundaries. +3. Replace local Promise wrappers with Effect helpers. +4. Convert `test(..., async () => { ... })` to `it.effect`, `it.instance`, + or `it.live`. 5. Move `await` calls inside `Effect.gen` as `yield*` calls. -6. Replace `await using tmp = await tmpdir(...)` with `yield* tmpdirScoped(...)` when the temp directory is inside an Effect test. -7. Replace `WithInstance.provide({ directory, fn })` with `it.instance(...)`, `provideInstance(directory)(effect)`, or `provideTmpdirInstance(...)`. -8. Replace Promise failure assertions with Effect assertions: +6. Replace `await using tmp = await tmpdir(...)` with + `yield* tmpdirScoped(...)` when the temp directory lives inside the + Effect test. +7. Replace Promise failure assertions with `Effect.exit`, `Effect.flip`, or + focused assertion helpers. +8. Preserve concurrency with fibers, `Deferred`, and + `Effect.all(..., { concurrency: "unbounded" })`; do not accidentally + serialize formerly parallel behavior. +9. Run the focused test file and `bun typecheck` from `packages/opencode`. -```ts -const exit = yield * run(input).pipe(Effect.exit) -expect(Exit.isFailure(exit)).toBe(true) -``` +## Good Examples -This is correct but still verbose. Track repeated assertion shapes during migration so we can add small test assertion helpers later instead of copying low-level `Exit` plumbing everywhere. +Use current examples as patterns, but re-check them before copying because +test migrations are active: -9. Keep concurrency concurrent by using `Effect.forkScoped`, `Fiber.join`, `Deferred`, or `Effect.all(..., { concurrency: "unbounded" })` instead of serializing formerly parallel Promise work. -10. Run the focused test file and `bun typecheck` from `packages/opencode`. +- `test/effect/instance-state.test.ts` — scoped directories, instance + switching, disposal, and concurrency. +- `test/bus/bus-effect.test.ts` — `Deferred`, streams, scoped fibers. +- `test/agent/plugin-agent-regression.test.ts` — real service layers plus + fake boundary layers. +- `test/account/service.test.ts` — service-level live tests, typed errors, + fake HTTP clients. -## Good Examples +## Migration Queue Policy + +Do not maintain a long file checklist here. It goes stale quickly. + +When looking for the next target, search for current anti-patterns: + +```bash +git grep -n "Effect.runPromise\|ManagedRuntime\|Promise.withResolvers\|Bun.sleep\|WithInstance" -- packages/opencode/test +``` -Use these files as models: - -- `test/tool/write.test.ts`: strong `it.instance` tests, top-level `testEffect(...)`, and Effect-native test helpers. -- `test/effect/instance-state.test.ts`: good `it.live` use for scoped directories, instance switching, reload/disposal, and concurrency. -- `test/bus/bus-effect.test.ts`: good `Deferred`, streams, and scoped fibers. -- `test/tool/truncation.test.ts`: good configured runners and concise live service tests. -- `test/tool/repo_clone.test.ts`: good live git integration while staying inside Effect fixtures. -- `test/server/httpapi-instance.test.ts`: good scoped integration layer setup and live HTTP assertions. -- `test/account/service.test.ts`: good service-level live tests, `Effect.flip`, typed errors, and fake HTTP clients. -- `test/agent/plugin-agent-regression.test.ts`: good example of open real service layers plus reusable fake boundary layers. - -## Current Promise-Land Hotspots - -Start with files that already exercise Effect services but still manually run Promises: - -- `test/config/config.test.ts`: many `Effect.runPromise`, `tmpdir()`, and `WithInstance.provide(...)` patterns despite already having `const it = testEffect(layer)`. -- `test/tool/shell.test.ts`: custom `ManagedRuntime`, Promise test helpers, and instance setup around shell execution. -- `test/tool/edit.test.ts`: manual runtime helpers and Promise concurrency patterns that should become fibers/deferreds. -- `test/session/messages-pagination.test.ts`: local Promise service facade over `Session.defaultLayer`. -- `test/snapshot/snapshot.test.ts`: Promise helper with `provideInstance` around snapshot operations. -- `test/file/index.test.ts`: Promise wrappers for `File.Service` plus repeated temp instance setup. -- `test/provider/provider.test.ts`: `AppRuntime.runPromise` helpers and mutable env/config setup. -- `test/project/vcs.test.ts`: Promise event waiting and `AppRuntime.runPromise` around VCS service calls. - -## Migration Order - -1. Convert one small file with straightforward service calls and no race behavior. -2. Convert `config.test.ts` incrementally by cluster, not in one PR. -3. Extract additional `test/fake/*` boundary layers only when a second test needs the same fake. -4. Convert files with concurrency or watchers after the simple files, preserving timing semantics with `Deferred` and fibers. -5. Leave pure non-Effect utility tests alone unless converting the underlying code to Effect. - -## Claimable Checklist - -Use this as a migration queue. Each checkbox should be safe for one agent or one PR unless the notes say otherwise. Agents should claim one item, convert only that file or cluster, run the focused test file, run `bun typecheck`, and update this checklist in the PR description or follow-up note. - -- [ ] `test/file/index.test.ts`: straightforward service wrapper cleanup. Replace local Promise helpers with Effect helpers and use `it.instance` / `it.live` around existing temp instance cases. -- [ ] `test/session/messages-pagination.test.ts`: convert the local `run(...)` / `svc(...)` facade to `testEffect(Session.defaultLayer...)` and direct service yields. Good early target. -- [ ] `test/snapshot/snapshot.test.ts`: convert snapshot operations to `it.live` with `tmpdirScoped` / `provideInstance`. Keep git/filesystem behavior live. -- [ ] `test/project/vcs.test.ts`: convert `AppRuntime.runPromise` service calls first. Leave event/watcher timing intact until the first Effect version is stable. -- [ ] `test/provider/provider.test.ts` cluster 1: convert provider service tests that only read config/env and do not mutate global state heavily. -- [ ] `test/provider/provider.test.ts` cluster 2: convert tests with env/config mutation after introducing or reusing service-backed test seams. -- [ ] `test/tool/shell.test.ts`: replace custom `ManagedRuntime` with `testEffect`, keep as `it.live`, and preserve process behavior. -- [ ] `test/tool/edit.test.ts` cluster 1: convert straightforward edit/read/write cases and remove manual runtime helpers. -- [ ] `test/tool/edit.test.ts` cluster 2: convert concurrency/race tests using `Deferred`, fibers, and `Effect.all` without serializing behavior. -- [ ] `test/config/config.test.ts` setup pass: replace inline fake layers with shared `test/fake/*` layers where possible and turn Promise helpers into Effect helpers. -- [ ] `test/config/config.test.ts` cluster 1: convert simple config load/merge tests that only need one instance. -- [ ] `test/config/config.test.ts` cluster 2: convert managed/global config tests that mutate `Global.Path` or managed config directories. Prefer service seams; use finalizers only as a bridge. -- [ ] `test/config/config.test.ts` cluster 3: convert plugin/dependency tests after ensuring `NpmTest.noop` or explicit fake NPM layers are used. -- [ ] `test/config/config.test.ts` cluster 4: convert remote/account/provider config tests after isolating auth/account/env dependencies through layers. -- [ ] Audit remaining `Effect.runPromise` in `packages/opencode/test/**/*.ts` and create follow-up checklist entries for any missed files. -- [ ] Audit remaining `WithInstance.provide` in `packages/opencode/test/**/*.ts` and convert cases that can use `it.instance` or `provideInstance` inside Effect. -- [ ] Audit repeated `Exit` / `Cause` assertion shapes and propose `test/lib/effect-assert.ts` helpers if at least three files repeat the same pattern. - -Parallelization notes: - -- The first four items are mostly independent and good for separate worktrees. -- `provider.test.ts`, `tool/edit.test.ts`, and `config.test.ts` should be split by cluster so agents do not edit the same file concurrently. -- Any new fake boundary layer under `test/fake/*` should be small and independently useful. Do not add a fake just for one assertion unless it removes a real external dependency. -- Do not combine assertion-helper design with file migrations. First collect repeated shapes, then add helpers in a separate pass. - -Orchestration rules: - -- Prefer supervised foreground agents for implementation. Background agents are acceptable for research-only surveys, but code migrations need a returned diff, focused test output, and local commit before moving on. -- Create one worktree per claim and verify the branch/worktree path before edits. A status check should include `git status --short --branch` from the claimed worktree. -- After an agent reports completion, the coordinator must independently inspect `git status`, run the focused test, run `bun typecheck`, and review the diff before pushing. -- If an agent edits the wrong worktree, move the patch deliberately with `git diff` / `git apply`, then clean the accidental worktree before opening a PR. -- Keep dependency setup boring. Prefer reusing existing installed dependencies via worktrees or symlinks over running a fresh `bun install` in a temporary path unless the native build path is known to work. -- Do not delete worktrees with unpushed commits or uncommitted changes. Once a migration PR branch is pushed and clean, the local worktree can be removed while leaving the branch on the fork. - -## Effectified Test Rough Edges - -Track patterns that are technically Effect-native but still too noisy. These should become a second cleanup pass after the Promise-land migration is underway. - -- Failure assertions against `Exit` / `Cause` are often verbose. Consider helpers such as `expectEffectFailure(effect)`, `expectTaggedError(effect, Tag)`, or custom Bun matchers if the same shapes repeat. -- Some tests still need `Effect.promise(...)` around Node/Bun filesystem helpers. Prefer Effect platform services when the surrounding code already uses them, but do not block migrations on perfect filesystem abstraction. -- Scoped global mutation with `process.env`, `Global.Path`, or flags should disappear behind injectable services over time. -- Layer composition can be noisy when a test needs a real service subtree plus fake boundaries. Keep extracting small `test/fake/*` boundary layers before inventing larger builders. -- Concurrency tests can become harder to read after replacing Promise resolvers with `Deferred` and fibers. Look for repeated patterns that deserve named helpers. +Then choose one file or one small cluster, keep the PR focused, and mention +the focused verification in the PR body. + +## Rough Edges To Watch + +- Failure assertions against `Exit` / `Cause` can get verbose. Add helpers + only after the same shape repeats across multiple files. +- Some tests still need `Effect.promise(...)` around Node/Bun APIs. Prefer + Effect platform services when the surrounding code already uses them, but + do not block useful migrations on perfect abstraction. +- Layer composition can be noisy when a test needs real service subtrees plus + fake boundaries. Extract small `test/fake/*` layers before inventing + larger builders. +- Concurrency tests can get harder to read after replacing Promise + resolvers. Look for repeated patterns that deserve named helpers. From e3684f36f9e0e011c93455f77c6ac2226b09413e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:46:41 -0400 Subject: [PATCH 206/378] chore: delete unused util/abort module + orphaned leak test (#27230) --- packages/opencode/src/util/abort.ts | 35 ----- .../test/memory/abort-leak-webfetch.ts | 49 ------- .../opencode/test/memory/abort-leak.test.ts | 127 ------------------ 3 files changed, 211 deletions(-) delete mode 100644 packages/opencode/src/util/abort.ts delete mode 100644 packages/opencode/test/memory/abort-leak-webfetch.ts delete mode 100644 packages/opencode/test/memory/abort-leak.test.ts diff --git a/packages/opencode/src/util/abort.ts b/packages/opencode/src/util/abort.ts deleted file mode 100644 index 3e7cfd8b2..000000000 --- a/packages/opencode/src/util/abort.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Creates an AbortController that automatically aborts after a timeout. - * - * Uses bind() instead of arrow functions to avoid capturing the surrounding - * scope in closures. Arrow functions like `() => controller.abort()` capture - * request bodies and other large objects, preventing GC for the timer lifetime. - * - * @param ms Timeout in milliseconds - * @returns Object with controller, signal, and clearTimeout function - */ -export function abortAfter(ms: number) { - const controller = new AbortController() - const id = setTimeout(controller.abort.bind(controller), ms) - return { - controller, - signal: controller.signal, - clearTimeout: () => globalThis.clearTimeout(id), - } -} - -/** - * Combines multiple AbortSignals with a timeout. - * - * @param ms Timeout in milliseconds - * @param signals Additional signals to combine - * @returns Combined signal that aborts on timeout or when any input signal aborts - */ -export function abortAfterAny(ms: number, ...signals: AbortSignal[]) { - const timeout = abortAfter(ms) - const signal = AbortSignal.any([timeout.signal, ...signals]) - return { - signal, - clearTimeout: timeout.clearTimeout, - } -} diff --git a/packages/opencode/test/memory/abort-leak-webfetch.ts b/packages/opencode/test/memory/abort-leak-webfetch.ts deleted file mode 100644 index c3197f8dd..000000000 --- a/packages/opencode/test/memory/abort-leak-webfetch.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { abortAfterAny } from "../../src/util/abort" - -const MB = 1024 * 1024 -const ITERATIONS = 50 - -const heap = () => { - Bun.gc(true) - return process.memoryUsage().heapUsed / MB -} - -const server = Bun.serve({ - port: 0, - fetch() { - return new Response("hello from local", { - headers: { - "content-type": "text/plain", - }, - }) - }, -}) - -const url = `http://127.0.0.1:${server.port}` - -async function run() { - const { signal, clearTimeout } = abortAfterAny(30000, new AbortController().signal) - try { - const response = await fetch(url, { signal }) - await response.text() - } finally { - clearTimeout() - } -} - -try { - await run() - Bun.sleepSync(100) - const baseline = heap() - - for (let i = 0; i < ITERATIONS; i++) { - await run() - } - - Bun.sleepSync(100) - const after = heap() - process.stdout.write(JSON.stringify({ baseline, after, growth: after - baseline })) -} finally { - void server.stop(true) - process.exit(0) -} diff --git a/packages/opencode/test/memory/abort-leak.test.ts b/packages/opencode/test/memory/abort-leak.test.ts deleted file mode 100644 index d30ad45e4..000000000 --- a/packages/opencode/test/memory/abort-leak.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { describe, test, expect } from "bun:test" -import path from "path" - -const projectRoot = path.join(import.meta.dir, "../..") -const worker = path.join(import.meta.dir, "abort-leak-webfetch.ts") - -const MB = 1024 * 1024 -const ITERATIONS = 50 - -const getHeapMB = () => { - Bun.gc(true) - return process.memoryUsage().heapUsed / MB -} - -describe("memory: abort controller leak", () => { - test("webfetch does not leak memory over many invocations", async () => { - // Measure the abort-timed fetch path in a fresh process so shared tool - // runtime state does not dominate the heap signal. - const proc = Bun.spawn({ - cmd: [process.execPath, worker], - cwd: projectRoot, - stdout: "pipe", - stderr: "pipe", - env: process.env, - }) - - const [code, stdout, stderr] = await Promise.all([ - proc.exited, - new Response(proc.stdout).text(), - new Response(proc.stderr).text(), - ]) - - if (code !== 0) { - throw new Error(stderr.trim() || stdout.trim() || `worker exited with code ${code}`) - } - - const result = JSON.parse(stdout.trim()) as { - baseline: number - after: number - growth: number - } - - console.log(`Baseline: ${result.baseline.toFixed(2)} MB`) - console.log(`After ${ITERATIONS} fetches: ${result.after.toFixed(2)} MB`) - console.log(`Growth: ${result.growth.toFixed(2)} MB`) - - // Memory growth should be minimal - less than 1MB per 10 requests. - expect(result.growth).toBeLessThan(ITERATIONS / 10) - }, 60000) - - test("compare closure vs bind pattern directly", async () => { - const ITERATIONS = 500 - - // Test OLD pattern: arrow function closure - // Store closures in a map keyed by content to force retention - const closureMap = new Map void>() - const timers: Timer[] = [] - const controllers: AbortController[] = [] - - Bun.gc(true) - Bun.sleepSync(100) - const baseline = getHeapMB() - - for (let i = 0; i < ITERATIONS; i++) { - // Simulate large response body like webfetch would have - const content = `${i}:${"x".repeat(50 * 1024)}` // 50KB unique per iteration - const controller = new AbortController() - controllers.push(controller) - - // OLD pattern - closure captures `content` - const handler = () => { - // Actually use content so it can't be optimized away - if (content.length > 1000000000) controller.abort() - } - closureMap.set(content, handler) - const timeoutId = setTimeout(handler, 30000) - timers.push(timeoutId) - } - - Bun.gc(true) - Bun.sleepSync(100) - const after = getHeapMB() - const oldGrowth = after - baseline - - console.log(`OLD pattern (closure): ${oldGrowth.toFixed(2)} MB growth (${closureMap.size} closures)`) - - // Cleanup after measuring - timers.forEach(clearTimeout) - controllers.forEach((c) => c.abort()) - closureMap.clear() - - // Test NEW pattern: bind - Bun.gc(true) - Bun.sleepSync(100) - const baseline2 = getHeapMB() - const handlers2: (() => void)[] = [] - const timers2: Timer[] = [] - const controllers2: AbortController[] = [] - - for (let i = 0; i < ITERATIONS; i++) { - const _content = `${i}:${"x".repeat(50 * 1024)}` // 50KB - won't be captured - const controller = new AbortController() - controllers2.push(controller) - - // NEW pattern - bind doesn't capture surrounding scope - const handler = controller.abort.bind(controller) - handlers2.push(handler) - const timeoutId = setTimeout(handler, 30000) - timers2.push(timeoutId) - } - - Bun.gc(true) - Bun.sleepSync(100) - const after2 = getHeapMB() - const newGrowth = after2 - baseline2 - - // Cleanup after measuring - timers2.forEach(clearTimeout) - controllers2.forEach((c) => c.abort()) - handlers2.length = 0 - - console.log(`NEW pattern (bind): ${newGrowth.toFixed(2)} MB growth`) - console.log(`Improvement: ${(oldGrowth - newGrowth).toFixed(2)} MB saved`) - - expect(newGrowth).toBeLessThanOrEqual(oldGrowth) - }) -}) From 03cf83323643dfbfb1abd452ca54b2b10273b295 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:51:11 -0400 Subject: [PATCH 207/378] test(provider): migrate DigitalOcean provider test to Effect runner (#27232) --- .../test/provider/digitalocean.test.ts | 228 +++++++++--------- 1 file changed, 109 insertions(+), 119 deletions(-) diff --git a/packages/opencode/test/provider/digitalocean.test.ts b/packages/opencode/test/provider/digitalocean.test.ts index 6fc49a6ef..665c792de 100644 --- a/packages/opencode/test/provider/digitalocean.test.ts +++ b/packages/opencode/test/provider/digitalocean.test.ts @@ -1,132 +1,122 @@ -import { test, expect, afterEach } from "bun:test" -import path from "path" - -import { tmpdir } from "../fixture/fixture" -import { WithInstance } from "../../src/project/with-instance" +import { expect } from "bun:test" import { Provider } from "../../src/provider/provider" import { ProviderID } from "../../src/provider/schema" -import { Env } from "../../src/env" import { Effect } from "effect" -import { AppRuntime } from "../../src/effect/app-runtime" -import { makeRuntime } from "../../src/effect/run-service" +import { testEffect } from "../lib/effect" -const envRuntime = makeRuntime(Env.Service, Env.defaultLayer) -const set = (k: string, v: string) => envRuntime.runSync((svc) => svc.set(k, v)) +const DIGITALOCEAN = ProviderID.make("digitalocean") +const it = testEffect(Provider.defaultLayer) -async function list() { - return AppRuntime.runPromise( - Effect.gen(function* () { - const provider = yield* Provider.Service - return yield* provider.list() +const withEnv = (values: Record, effect: Effect.Effect) => + Effect.acquireUseRelease( + Effect.sync(() => { + const previous = Object.fromEntries(Object.keys(values).map((key) => [key, process.env[key]] as const)) + Object.assign(process.env, values) + return previous }), + () => effect, + (previous) => + Effect.sync(() => { + for (const [key, value] of Object.entries(previous)) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + }), ) -} - -const DIGITALOCEAN = ProviderID.make("digitalocean") -const originalAuthContent = process.env.OPENCODE_AUTH_CONTENT -afterEach(() => { - if (originalAuthContent === undefined) delete process.env.OPENCODE_AUTH_CONTENT - else process.env.OPENCODE_AUTH_CONTENT = originalAuthContent -}) - -function injectAuth(metadata: Record | undefined) { - process.env.OPENCODE_AUTH_CONTENT = JSON.stringify({ - digitalocean: { - type: "api", - key: "sk_do_test", - ...(metadata ? { metadata } : {}), +const withAuth = (metadata: Record | undefined, effect: Effect.Effect) => + withEnv( + { + OPENCODE_AUTH_CONTENT: JSON.stringify({ + digitalocean: { + type: "api", + key: "sk_do_test", + ...(metadata ? { metadata } : {}), + }, + }), }, - }) -} + effect, + ) -test("digitalocean provider autoloads from DIGITALOCEAN_ACCESS_TOKEN", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - set("DIGITALOCEAN_ACCESS_TOKEN", "test-token") - const providers = await list() - expect(providers[DIGITALOCEAN]).toBeDefined() - expect(providers[DIGITALOCEAN].source).toBe("env") - const baseModel = Object.values(providers[DIGITALOCEAN].models)[0] - expect(baseModel.api.url).toBe("https://inference.do-ai.run/v1") - expect(baseModel.api.npm).toBe("@ai-sdk/openai-compatible") - const routerEntries = Object.keys(providers[DIGITALOCEAN].models).filter((id) => id.startsWith("router:")) - expect(routerEntries.length).toBe(0) - }, - }) -}) +it.instance( + "digitalocean provider autoloads from DIGITALOCEAN_ACCESS_TOKEN", + () => + withEnv( + { DIGITALOCEAN_ACCESS_TOKEN: "test-token" }, + Effect.gen(function* () { + const provider = yield* Provider.Service + const providers = yield* provider.list() + expect(providers[DIGITALOCEAN]).toBeDefined() + expect(providers[DIGITALOCEAN].source).toBe("env") + const baseModel = Object.values(providers[DIGITALOCEAN].models)[0] + expect(baseModel.api.url).toBe("https://inference.do-ai.run/v1") + expect(baseModel.api.npm).toBe("@ai-sdk/openai-compatible") + const routerEntries = Object.keys(providers[DIGITALOCEAN].models).filter((id) => id.startsWith("router:")) + expect(routerEntries.length).toBe(0) + }), + ), + { config: {} }, +) -test("digitalocean provider.models surfaces cached routers from auth metadata", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) - }, - }) - injectAuth({ - routers: JSON.stringify([ - { name: "my-router", uuid: "11f1499a-aaaa-bbbb-cccc-4e013e2ddde4" }, - { name: "other-router", uuid: "22f1499a-aaaa-bbbb-cccc-4e013e2ddde4" }, - ]), - routers_fetched_at: String(Date.now()), - oauth_access: "doo_v1_test", - oauth_expires: String(Date.now() + 60 * 60 * 1000), - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const providers = await list() - const models = providers[DIGITALOCEAN].models - expect(models["router:my-router"]).toBeDefined() - expect(models["router:my-router"].api.id).toBe("router:my-router") - expect(models["router:my-router"].api.url).toBe("https://inference.do-ai.run/v1") - expect(models["router:my-router"].api.npm).toBe("@ai-sdk/openai-compatible") - expect(models["router:other-router"]).toBeDefined() - }, - }) -}) +it.instance( + "digitalocean provider.models surfaces cached routers from auth metadata", + () => + withAuth( + { + routers: JSON.stringify([ + { name: "my-router", uuid: "11f1499a-aaaa-bbbb-cccc-4e013e2ddde4" }, + { name: "other-router", uuid: "22f1499a-aaaa-bbbb-cccc-4e013e2ddde4" }, + ]), + routers_fetched_at: String(Date.now()), + oauth_access: "doo_v1_test", + oauth_expires: String(Date.now() + 60 * 60 * 1000), + }, + Effect.gen(function* () { + const provider = yield* Provider.Service + const providers = yield* provider.list() + const models = providers[DIGITALOCEAN].models + expect(models["router:my-router"]).toBeDefined() + expect(models["router:my-router"].api.id).toBe("router:my-router") + expect(models["router:my-router"].api.url).toBe("https://inference.do-ai.run/v1") + expect(models["router:my-router"].api.npm).toBe("@ai-sdk/openai-compatible") + expect(models["router:other-router"]).toBeDefined() + }), + ), + { config: {} }, +) -test("digitalocean provider.models skips refresh when oauth bearer is expired", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) - }, - }) - injectAuth({ - routers: JSON.stringify([{ name: "stale-router", uuid: "stale" }]), - routers_fetched_at: "0", - oauth_access: "doo_v1_expired", - oauth_expires: "1", - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const providers = await list() - const models = providers[DIGITALOCEAN].models - expect(models["router:stale-router"]).toBeDefined() - }, - }) -}) +it.instance( + "digitalocean provider.models skips refresh when oauth bearer is expired", + () => + withAuth( + { + routers: JSON.stringify([{ name: "stale-router", uuid: "stale" }]), + routers_fetched_at: "0", + oauth_access: "doo_v1_expired", + oauth_expires: "1", + }, + Effect.gen(function* () { + const provider = yield* Provider.Service + const providers = yield* provider.list() + const models = providers[DIGITALOCEAN].models + expect(models["router:stale-router"]).toBeDefined() + }), + ), + { config: {} }, +) -test("digitalocean provider.models passes through base models when no auth metadata", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - set("DIGITALOCEAN_ACCESS_TOKEN", "test-token") - const providers = await list() - const models = providers[DIGITALOCEAN].models - expect(Object.keys(models).length).toBeGreaterThan(0) - expect(Object.keys(models).filter((id) => id.startsWith("router:")).length).toBe(0) - }, - }) -}) +it.instance( + "digitalocean provider.models passes through base models when no auth metadata", + () => + withEnv( + { DIGITALOCEAN_ACCESS_TOKEN: "test-token" }, + Effect.gen(function* () { + const provider = yield* Provider.Service + const providers = yield* provider.list() + const models = providers[DIGITALOCEAN].models + expect(Object.keys(models).length).toBeGreaterThan(0) + expect(Object.keys(models).filter((id) => id.startsWith("router:")).length).toBe(0) + }), + ), + { config: {} }, +) From 485ecbde18f5b164273cf086057a062be9efe3e6 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:52:59 -0400 Subject: [PATCH 208/378] test(server): migrate global session list to effect runner (#27233) --- .../test/server/global-session-list.test.ts | 188 +++++++++--------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/packages/opencode/test/server/global-session-list.test.ts b/packages/opencode/test/server/global-session-list.test.ts index 04348e5c0..0fdba1f66 100644 --- a/packages/opencode/test/server/global-session-list.test.ts +++ b/packages/opencode/test/server/global-session-list.test.ts @@ -1,104 +1,104 @@ -import { describe, expect, test } from "bun:test" -import { Effect } from "effect" -import { WithInstance } from "../../src/project/with-instance" +import { describe, expect } from "bun:test" +import { Deferred, Effect, Layer } from "effect" import { Project } from "@/project/project" import { Session as SessionNs } from "@/session/session" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import * as Log from "@opencode-ai/core/util/log" -import { tmpdir } from "../fixture/fixture" +import { provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" void Log.init({ print: false }) -function run(fx: Effect.Effect) { - return Effect.runPromise(fx.pipe(Effect.provide(SessionNs.defaultLayer))) -} +const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, Project.defaultLayer, CrossSpawnSpawner.defaultLayer)) -const svc = { - ...SessionNs, - create(input?: SessionNs.CreateInput) { - return run(SessionNs.Service.use((svc) => svc.create(input))) - }, - setArchived(input: typeof SessionNs.SetArchivedInput.Type) { - return run(SessionNs.Service.use((svc) => svc.setArchived(input))) - }, -} +const withSession = (input?: Parameters[0]) => + Effect.acquireRelease( + SessionNs.Service.use((session) => session.create(input)), + (created) => SessionNs.Service.use((session) => session.remove(created.id).pipe(Effect.ignore)), + ) describe("session.listGlobal", () => { - test("lists sessions across projects with project metadata", async () => { - await using first = await tmpdir({ git: true }) - await using second = await tmpdir({ git: true }) - - const firstSession = await WithInstance.provide({ - directory: first.path, - fn: async () => svc.create({ title: "first-session" }), - }) - const secondSession = await WithInstance.provide({ - directory: second.path, - fn: async () => svc.create({ title: "second-session" }), - }) - - const sessions = [...svc.listGlobal({ limit: 200 })] - const ids = sessions.map((session) => session.id) - - expect(ids).toContain(firstSession.id) - expect(ids).toContain(secondSession.id) - - const firstProject = Project.get(firstSession.projectID) - const secondProject = Project.get(secondSession.projectID) - - const firstItem = sessions.find((session) => session.id === firstSession.id) - const secondItem = sessions.find((session) => session.id === secondSession.id) - - expect(firstItem?.project?.id).toBe(firstProject?.id) - expect(firstItem?.project?.worktree).toBe(firstProject?.worktree) - expect(secondItem?.project?.id).toBe(secondProject?.id) - expect(secondItem?.project?.worktree).toBe(secondProject?.worktree) - }) - - test("excludes archived sessions by default", async () => { - await using tmp = await tmpdir({ git: true }) - - const archived = await WithInstance.provide({ - directory: tmp.path, - fn: async () => svc.create({ title: "archived-session" }), - }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => svc.setArchived({ sessionID: archived.id, time: Date.now() }), - }) - - const sessions = [...svc.listGlobal({ limit: 200 })] - const ids = sessions.map((session) => session.id) - - expect(ids).not.toContain(archived.id) - - const allSessions = [...svc.listGlobal({ limit: 200, archived: true })] - const allIds = allSessions.map((session) => session.id) - - expect(allIds).toContain(archived.id) - }) - - test("supports cursor pagination", async () => { - await using tmp = await tmpdir({ git: true }) - - const first = await WithInstance.provide({ - directory: tmp.path, - fn: async () => svc.create({ title: "page-one" }), - }) - await new Promise((resolve) => setTimeout(resolve, 5)) - const second = await WithInstance.provide({ - directory: tmp.path, - fn: async () => svc.create({ title: "page-two" }), - }) - - const page = [...svc.listGlobal({ directory: tmp.path, limit: 1 })] - expect(page.length).toBe(1) - expect(page[0].id).toBe(second.id) - - const next = [...svc.listGlobal({ directory: tmp.path, limit: 10, cursor: page[0].time.updated })] - const ids = next.map((session) => session.id) - - expect(ids).toContain(first.id) - expect(ids).not.toContain(second.id) - }) + it.instance( + "lists sessions across projects with project metadata", + () => + Effect.gen(function* () { + const first = yield* TestInstance + const second = yield* tmpdirScoped({ git: true }) + + const firstSession = yield* withSession({ title: "first-session" }) + const secondSession = yield* withSession({ title: "second-session" }).pipe(provideInstance(second)) + + const sessions = yield* Effect.sync(() => [...SessionNs.listGlobal({ limit: 200 })]) + const ids = sessions.map((session) => session.id) + + expect(ids).toContain(firstSession.id) + expect(ids).toContain(secondSession.id) + + const firstProject = yield* Project.Service.use((project) => project.get(firstSession.projectID)) + const secondProject = yield* Project.Service.use((project) => project.get(secondSession.projectID)) + + const firstItem = sessions.find((session) => session.id === firstSession.id) + const secondItem = sessions.find((session) => session.id === secondSession.id) + + expect(firstItem?.project?.id).toBe(firstProject?.id) + expect(firstItem?.project?.worktree).toBe(firstProject?.worktree) + expect(secondItem?.project?.id).toBe(secondProject?.id) + expect(secondItem?.project?.worktree).toBe(secondProject?.worktree) + expect(first.directory).not.toBe(second) + }), + { git: true }, + ) + + it.instance( + "excludes archived sessions by default", + () => + Effect.gen(function* () { + const archived = yield* withSession({ title: "archived-session" }) + + yield* SessionNs.Service.use((session) => session.setArchived({ sessionID: archived.id, time: Date.now() })) + + const sessions = yield* Effect.sync(() => [...SessionNs.listGlobal({ limit: 200 })]) + const ids = sessions.map((session) => session.id) + + expect(ids).not.toContain(archived.id) + + const allSessions = yield* Effect.sync(() => [...SessionNs.listGlobal({ limit: 200, archived: true })]) + const allIds = allSessions.map((session) => session.id) + + expect(allIds).toContain(archived.id) + }), + { git: true }, + ) + + it.instance( + "supports cursor pagination", + () => + Effect.gen(function* () { + const test = yield* TestInstance + + const first = yield* withSession({ title: "page-one" }) + const ready = yield* Deferred.make() + yield* Deferred.succeed(ready, undefined).pipe(Effect.delay("5 millis"), Effect.forkScoped) + yield* Deferred.await(ready).pipe( + Effect.timeoutOrElse({ + duration: "1 second", + orElse: () => Effect.fail(new Error("timed out waiting between session creates")), + }), + ) + const second = yield* withSession({ title: "page-two" }) + + const page = yield* Effect.sync(() => [...SessionNs.listGlobal({ directory: test.directory, limit: 1 })]) + expect(page.length).toBe(1) + expect(page[0].id).toBe(second.id) + + const next = yield* Effect.sync(() => [ + ...SessionNs.listGlobal({ directory: test.directory, limit: 10, cursor: page[0].time.updated }), + ]) + const ids = next.map((session) => session.id) + + expect(ids).toContain(first.id) + expect(ids).not.toContain(second.id) + }), + { git: true }, + ) }) From 0b67e1a046b62177889b2e3a02a85239fd504ce0 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:55:33 -0400 Subject: [PATCH 209/378] test(server): migrate session messages to Effect runner (#27234) --- .../test/server/session-messages.test.ts | 316 +++++++++--------- 1 file changed, 152 insertions(+), 164 deletions(-) diff --git a/packages/opencode/test/server/session-messages.test.ts b/packages/opencode/test/server/session-messages.test.ts index f5ee5bdcb..e603accbb 100644 --- a/packages/opencode/test/server/session-messages.test.ts +++ b/packages/opencode/test/server/session-messages.test.ts @@ -1,191 +1,179 @@ -import { afterEach, describe, expect, test } from "bun:test" +import { afterEach, describe, expect } from "bun:test" import { Effect } from "effect" -import { WithInstance } from "../../src/project/with-instance" import { Server } from "../../src/server/server" import { Session as SessionNs } from "@/session/session" import { MessageV2 } from "../../src/session/message-v2" +import { ModelID, ProviderID } from "../../src/provider/schema" import { MessageID, PartID, type SessionID } from "../../src/session/schema" import * as Log from "@opencode-ai/core/util/log" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" void Log.init({ print: false }) -function run(fx: Effect.Effect) { - return Effect.runPromise(fx.pipe(Effect.provide(SessionNs.defaultLayer))) -} +const it = testEffect(SessionNs.defaultLayer) -const svc = { - ...SessionNs, - create(input?: SessionNs.CreateInput) { - return run(SessionNs.Service.use((svc) => svc.create(input))) - }, - remove(id: SessionID) { - return run(SessionNs.Service.use((svc) => svc.remove(id))) - }, - updateMessage(msg: T) { - return run(SessionNs.Service.use((svc) => svc.updateMessage(msg))) - }, - updatePart(part: T) { - return run(SessionNs.Service.use((svc) => svc.updatePart(part))) - }, +const model = { + providerID: ProviderID.make("test"), + modelID: ModelID.make("test"), } afterEach(async () => { await disposeAllInstances() }) -async function withoutWatcher(fn: () => Promise) { - if (process.platform !== "win32") return fn() - const prev = process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER - process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = "true" - try { - return await fn() - } finally { - if (prev === undefined) delete process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER - else process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = prev - } +const withoutWatcher = (effect: Effect.Effect) => { + if (process.platform !== "win32") return effect + return Effect.acquireUseRelease( + Effect.sync(() => { + const previous = process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER + process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = "true" + return previous + }), + () => effect, + (previous) => + Effect.sync(() => { + if (previous === undefined) delete process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER + else process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = previous + }), + ) +} + +const sessionScoped = Effect.acquireRelease( + SessionNs.Service.use((svc) => svc.create({})), + (session) => SessionNs.Service.use((svc) => svc.remove(session.id)).pipe(Effect.ignore), +) + +const fill = Effect.fn("SessionMessagesTest.fill")(function* ( + sessionID: SessionID, + count: number, + time = (i: number) => Date.now() + i, +) { + const session = yield* SessionNs.Service + return yield* Effect.forEach( + Array.from({ length: count }, (_, i) => i), + (i) => + Effect.gen(function* () { + const id = MessageID.ascending() + yield* session.updateMessage({ + id, + sessionID, + role: "user", + time: { created: time(i) }, + agent: "test", + model, + tools: {}, + } satisfies MessageV2.User) + yield* session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: `m${i}`, + } satisfies MessageV2.TextPart) + return id + }), + ) +}) + +function request(path: string) { + return Effect.promise(() => Promise.resolve(Server.Default().app.request(path))) } -async function fill(sessionID: SessionID, count: number, time = (i: number) => Date.now() + i) { - const ids = [] as MessageID[] - for (let i = 0; i < count; i++) { - const id = MessageID.ascending() - ids.push(id) - await svc.updateMessage({ - id, - sessionID, - role: "user", - time: { created: time(i) }, - agent: "test", - model: { providerID: "test", modelID: "test" }, - tools: {}, - mode: "", - } as unknown as MessageV2.Info) - await svc.updatePart({ - id: PartID.ascending(), - sessionID, - messageID: id, - type: "text", - text: `m${i}`, - }) - } - return ids +function json(response: Response) { + return Effect.promise(() => response.json() as Promise) } describe("session messages endpoint", () => { - test("returns cursor headers for older pages", async () => { - await using tmp = await tmpdir({ git: true }) - await withoutWatcher(() => - WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 5) - const app = Server.Default().app - - const a = await app.request(`/session/${session.id}/message?limit=2`) - expect(a.status).toBe(200) - const aBody = (await a.json()) as MessageV2.WithParts[] - expect(aBody.map((item) => item.info.id)).toEqual(ids.slice(-2)) - const cursor = a.headers.get("x-next-cursor") - expect(cursor).toBeTruthy() - expect(a.headers.get("link")).toContain('rel="next"') - - const b = await app.request(`/session/${session.id}/message?limit=2&before=${encodeURIComponent(cursor!)}`) - expect(b.status).toBe(200) - const bBody = (await b.json()) as MessageV2.WithParts[] - expect(bBody.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) - - await svc.remove(session.id) - }, + it.instance( + "returns cursor headers for older pages", + withoutWatcher( + Effect.gen(function* () { + const session = yield* sessionScoped + const ids = yield* fill(session.id, 5) + + const a = yield* request(`/session/${session.id}/message?limit=2`) + expect(a.status).toBe(200) + const aBody = yield* json(a) + expect(aBody.map((item) => item.info.id)).toEqual(ids.slice(-2)) + const cursor = a.headers.get("x-next-cursor") + expect(cursor).toBeTruthy() + expect(a.headers.get("link")).toContain('rel="next"') + + const b = yield* request(`/session/${session.id}/message?limit=2&before=${encodeURIComponent(cursor!)}`) + expect(b.status).toBe(200) + const bBody = yield* json(b) + expect(bBody.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) }), - ) - }) - - test("keeps full-history responses when limit is omitted", async () => { - await using tmp = await tmpdir({ git: true }) - await withoutWatcher(() => - WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const session = await svc.create({}) - const ids = await fill(session.id, 3) - const app = Server.Default().app - - const res = await app.request(`/session/${session.id}/message`) - expect(res.status).toBe(200) - const body = (await res.json()) as MessageV2.WithParts[] - expect(body.map((item) => item.info.id)).toEqual(ids) - - await svc.remove(session.id) - }, + ), + { git: true }, + ) + + it.instance( + "keeps full-history responses when limit is omitted", + withoutWatcher( + Effect.gen(function* () { + const session = yield* sessionScoped + const ids = yield* fill(session.id, 3) + + const res = yield* request(`/session/${session.id}/message`) + expect(res.status).toBe(200) + const body = yield* json(res) + expect(body.map((item) => item.info.id)).toEqual(ids) }), - ) - }) - - test("rejects invalid cursors and missing sessions", async () => { - await using tmp = await tmpdir({ git: true }) - await withoutWatcher(() => - WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const session = await svc.create({}) - const app = Server.Default().app - - const bad = await app.request(`/session/${session.id}/message?limit=2&before=bad`) - expect(bad.status).toBe(400) - - const miss = await app.request(`/session/ses_missing/message?limit=2`) - expect(miss.status).toBe(404) - - await svc.remove(session.id) - }, + ), + { git: true }, + ) + + it.instance( + "rejects invalid cursors and missing sessions", + withoutWatcher( + Effect.gen(function* () { + const session = yield* sessionScoped + + const bad = yield* request(`/session/${session.id}/message?limit=2&before=bad`) + expect(bad.status).toBe(400) + + const miss = yield* request(`/session/ses_missing/message?limit=2`) + expect(miss.status).toBe(404) }), - ) - }) - - test("does not truncate large legacy limit requests", async () => { - await using tmp = await tmpdir({ git: true }) - await withoutWatcher(() => - WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const session = await svc.create({}) - await fill(session.id, 520) - const app = Server.Default().app - - const res = await app.request(`/session/${session.id}/message?limit=510`) - expect(res.status).toBe(200) - const body = (await res.json()) as MessageV2.WithParts[] - expect(body).toHaveLength(510) - - await svc.remove(session.id) - }, + ), + { git: true }, + ) + + it.instance( + "does not truncate large legacy limit requests", + withoutWatcher( + Effect.gen(function* () { + const session = yield* sessionScoped + yield* fill(session.id, 520) + + const res = yield* request(`/session/${session.id}/message?limit=510`) + expect(res.status).toBe(200) + const body = yield* json(res) + expect(body).toHaveLength(510) }), - ) - }) - - test("accepts directory query used by workspace routing", async () => { - await using tmp = await tmpdir({ git: true }) - await withoutWatcher(() => - WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const session = await svc.create({}) - await fill(session.id, 1) - const app = Server.Default().app - - const res = await app.request( - `/session/${session.id}/message?limit=80&directory=${encodeURIComponent(tmp.path)}`, - ) - expect(res.status).toBe(200) - const body = await res.json() - expect(Array.isArray(body)).toBe(true) - expect(body).toHaveLength(1) - - await svc.remove(session.id) - }, + ), + { git: true }, + ) + + it.instance( + "accepts directory query used by workspace routing", + withoutWatcher( + Effect.gen(function* () { + const tmp = yield* TestInstance + const session = yield* sessionScoped + yield* fill(session.id, 1) + + const res = yield* request( + `/session/${session.id}/message?limit=80&directory=${encodeURIComponent(tmp.directory)}`, + ) + expect(res.status).toBe(200) + const body = yield* json(res) + expect(Array.isArray(body)).toBe(true) + expect(body).toHaveLength(1) }), - ) - }) + ), + { git: true }, + ) }) From 8370d0cef4c3362674f480b7fff0b42f03ed84fd Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:55:36 -0400 Subject: [PATCH 210/378] test(server): migrate httpapi ui tests to effect runner (#27236) --- .../opencode/test/server/httpapi-ui.test.ts | 429 +++++++++--------- 1 file changed, 227 insertions(+), 202 deletions(-) diff --git a/packages/opencode/test/server/httpapi-ui.test.ts b/packages/opencode/test/server/httpapi-ui.test.ts index 256c45019..74fc23004 100644 --- a/packages/opencode/test/server/httpapi-ui.test.ts +++ b/packages/opencode/test/server/httpapi-ui.test.ts @@ -1,5 +1,5 @@ import { createHash } from "node:crypto" -import { afterEach, describe, expect, test } from "bun:test" +import { describe, expect } from "bun:test" import { Flag } from "@opencode-ai/core/flag/flag" import * as Log from "@opencode-ai/core/util/log" import { ConfigProvider, Effect, Layer } from "effect" @@ -18,24 +18,33 @@ import { authorizationRouterMiddleware } from "../../src/server/routes/instance/ import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { serveEmbeddedUIEffect, serveUIEffect } from "../../src/server/shared/ui" import { Server } from "../../src/server/server" +import { testEffect } from "../lib/effect" void Log.init({ print: false }) -const original = { - OPENCODE_DISABLE_EMBEDDED_WEB_UI: Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI, - OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, - OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, - envPassword: process.env.OPENCODE_SERVER_PASSWORD, - envUsername: process.env.OPENCODE_SERVER_USERNAME, -} +const testStateLayer = Layer.effectDiscard( + Effect.gen(function* () { + const original = { + OPENCODE_DISABLE_EMBEDDED_WEB_UI: Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI, + OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, + envPassword: process.env.OPENCODE_SERVER_PASSWORD, + envUsername: process.env.OPENCODE_SERVER_USERNAME, + } -afterEach(() => { - Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = original.OPENCODE_DISABLE_EMBEDDED_WEB_UI - Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD - Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME - restoreEnv("OPENCODE_SERVER_PASSWORD", original.envPassword) - restoreEnv("OPENCODE_SERVER_USERNAME", original.envUsername) -}) + yield* Effect.addFinalizer(() => + Effect.sync(() => { + Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = original.OPENCODE_DISABLE_EMBEDDED_WEB_UI + Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME + restoreEnv("OPENCODE_SERVER_PASSWORD", original.envPassword) + restoreEnv("OPENCODE_SERVER_USERNAME", original.envUsername) + }), + ) + }), +) + +const it = testEffect(Layer.mergeAll(testStateLayer, AppFileSystem.defaultLayer)) function restoreEnv(key: string, value: string | undefined) { if (value === undefined) { @@ -61,9 +70,13 @@ function app(input?: { password?: string; username?: string }) { ).handler return { request(input: string | URL | Request, init?: RequestInit) { - return handler( - input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init), - ExperimentalHttpApiServer.context, + return Effect.promise(() => + Promise.resolve( + handler( + input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init), + ExperimentalHttpApiServer.context, + ), + ), ) }, } @@ -95,9 +108,13 @@ function uiApp(input?: { password?: string; username?: string; client?: Layer.La ).handler return { request(input: string | URL | Request, init?: RequestInit) { - return handler( - input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init), - ExperimentalHttpApiServer.context, + return Effect.promise(() => + Promise.resolve( + handler( + input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init), + ExperimentalHttpApiServer.context, + ), + ), ) }, } @@ -113,32 +130,38 @@ function httpClient(response: Response, onRequest?: (request: HttpClientRequest. ) } +function responseText(response: Response) { + return Effect.promise(() => response.text()) +} + describe("HttpApi UI fallback", () => { - test("serves the web UI through the experimental backend", async () => { - Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true - let proxiedUrl: string | undefined - - const response = await uiApp({ - client: httpClient( - new Response("opencode", { headers: { "content-type": "text/html" } }), - (request) => { - proxiedUrl = request.url - }, - ), - }).request("/") + it.live("serves the web UI through the experimental backend", () => + Effect.gen(function* () { + Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true + let proxiedUrl: string | undefined + + const response = yield* uiApp({ + client: httpClient( + new Response("opencode", { headers: { "content-type": "text/html" } }), + (request) => { + proxiedUrl = request.url + }, + ), + }).request("/") - expect(response.status).toBe(200) - expect(response.headers.get("content-type")).toContain("text/html") - expect(await response.text()).toBe("opencode") - expect(proxiedUrl).toBe("https://app.opencode.ai/") - }) + expect(response.status).toBe(200) + expect(response.headers.get("content-type")).toContain("text/html") + expect(yield* responseText(response)).toBe("opencode") + expect(proxiedUrl).toBe("https://app.opencode.ai/") + }), + ) - test("strips upstream transfer encoding headers from proxied assets", async () => { - Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true - let proxiedUrl: string | undefined + it.live("strips upstream transfer encoding headers from proxied assets", () => + Effect.gen(function* () { + Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true + let proxiedUrl: string | undefined - const response = await Effect.runPromise( - Effect.gen(function* () { + const response = yield* Effect.gen(function* () { const fs = yield* AppFileSystem.Service const client = yield* HttpClient.HttpClient return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/assets/app.js")), { @@ -147,48 +170,45 @@ describe("HttpApi UI fallback", () => { }) }).pipe( Effect.provide( - Layer.mergeAll( - AppFileSystem.defaultLayer, - Layer.succeed( - HttpClient.HttpClient, - HttpClient.make((request) => { - proxiedUrl = request.url - return Effect.succeed( - HttpClientResponse.fromWeb( - request, - new Response("console.log('ok')", { - headers: { - "content-encoding": "br", - "content-length": "999", - "content-type": "text/javascript", - }, - }), - ), - ) - }), - ), + Layer.succeed( + HttpClient.HttpClient, + HttpClient.make((request) => { + proxiedUrl = request.url + return Effect.succeed( + HttpClientResponse.fromWeb( + request, + new Response("console.log('ok')", { + headers: { + "content-encoding": "br", + "content-length": "999", + "content-type": "text/javascript", + }, + }), + ), + ) + }), ), ), Effect.map(HttpServerResponse.toWeb), - ), - ) + ) - expect(response.status).toBe(200) - expect(proxiedUrl).toBe("https://app.opencode.ai/assets/app.js") - expect(response.headers.get("content-encoding")).toBeNull() - expect(response.headers.get("content-length")).not.toBe("999") - expect(response.headers.get("content-type")).toContain("text/javascript") - expect(await response.text()).toBe("console.log('ok')") - }) + expect(response.status).toBe(200) + expect(proxiedUrl).toBe("https://app.opencode.ai/assets/app.js") + expect(response.headers.get("content-encoding")).toBeNull() + expect(response.headers.get("content-length")).not.toBe("999") + expect(response.headers.get("content-type")).toContain("text/javascript") + expect(yield* responseText(response)).toBe("console.log('ok')") + }), + ) // Regression for #25698 (Ope): upstream `transfer-encoding: chunked` was // forwarded through the proxy while the proxy itself re-frames the body, // causing browsers to fail with `ERR_INVALID_CHUNKED_ENCODING`. - test("strips upstream transfer-encoding header from proxied assets", async () => { - Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true + it.live("strips upstream transfer-encoding header from proxied assets", () => + Effect.gen(function* () { + Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true - const response = await Effect.runPromise( - Effect.gen(function* () { + const response = yield* Effect.gen(function* () { const fs = yield* AppFileSystem.Service const client = yield* HttpClient.HttpClient return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/")), { @@ -197,161 +217,166 @@ describe("HttpApi UI fallback", () => { }) }).pipe( Effect.provide( - Layer.mergeAll( - AppFileSystem.defaultLayer, - Layer.succeed( - HttpClient.HttpClient, - HttpClient.make((request) => - Effect.succeed( - HttpClientResponse.fromWeb( - request, - new Response("opencode", { - headers: { - "transfer-encoding": "chunked", - "content-type": "text/html", - }, - }), - ), + Layer.succeed( + HttpClient.HttpClient, + HttpClient.make((request) => + Effect.succeed( + HttpClientResponse.fromWeb( + request, + new Response("opencode", { + headers: { + "transfer-encoding": "chunked", + "content-type": "text/html", + }, + }), ), ), ), ), ), Effect.map(HttpServerResponse.toWeb), - ), - ) - - expect(response.status).toBe(200) - expect(response.headers.get("transfer-encoding")).toBeNull() - expect(await response.text()).toBe("opencode") - }) + ) - test("serves embedded UI assets when Bun can read them but access reports missing", async () => { - let readPath: string | undefined + expect(response.status).toBe(200) + expect(response.headers.get("transfer-encoding")).toBeNull() + expect(yield* responseText(response)).toBe("opencode") + }), + ) - const response = await Effect.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* serveEmbeddedUIEffect( - "/assets/app.js", - { - ...fs, - existsSafe: () => Effect.die("embedded UI should not rely on filesystem access checks"), - readFile: (path) => { - readPath = path - return path === "/$bunfs/root/assets/app.js" - ? Effect.succeed(new TextEncoder().encode("console.log('embedded')")) - : Effect.die(`unexpected embedded UI path: ${path}`) - }, + it.live("serves embedded UI assets when Bun can read them but access reports missing", () => + Effect.gen(function* () { + let readPath: string | undefined + + const fs = yield* AppFileSystem.Service + const response = yield* serveEmbeddedUIEffect( + "/assets/app.js", + { + ...fs, + existsSafe: () => Effect.die("embedded UI should not rely on filesystem access checks"), + readFile: (path) => { + readPath = path + return path === "/$bunfs/root/assets/app.js" + ? Effect.succeed(new TextEncoder().encode("console.log('embedded')")) + : Effect.die(`unexpected embedded UI path: ${path}`) }, - { "assets/app.js": "/$bunfs/root/assets/app.js" }, - ) - }).pipe(Effect.provide(AppFileSystem.defaultLayer), Effect.map(HttpServerResponse.toWeb)), - ) - - expect(response.status).toBe(200) - expect(readPath).toBe("/$bunfs/root/assets/app.js") - expect(response.headers.get("content-type")).toContain("text/javascript") - expect(await response.text()).toBe("console.log('embedded')") - }) + }, + { "assets/app.js": "/$bunfs/root/assets/app.js" }, + ).pipe(Effect.map(HttpServerResponse.toWeb)) - test("allows embedded UI terminal wasm and theme preload CSP", async () => { - const script = 'document.documentElement.dataset.theme = "dark"' + expect(response.status).toBe(200) + expect(readPath).toBe("/$bunfs/root/assets/app.js") + expect(response.headers.get("content-type")).toContain("text/javascript") + expect(yield* responseText(response)).toBe("console.log('embedded')") + }), + ) - const response = await Effect.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* serveEmbeddedUIEffect( - "/", - { - ...fs, - readFile: (path) => { - return path === "/$bunfs/root/index.html" - ? Effect.succeed( - new TextEncoder().encode( - ``, - ), - ) - : Effect.die(`unexpected embedded UI path: ${path}`) - }, + it.live("allows embedded UI terminal wasm and theme preload CSP", () => + Effect.gen(function* () { + const script = 'document.documentElement.dataset.theme = "dark"' + + const fs = yield* AppFileSystem.Service + const response = yield* serveEmbeddedUIEffect( + "/", + { + ...fs, + readFile: (path) => { + return path === "/$bunfs/root/index.html" + ? Effect.succeed( + new TextEncoder().encode( + ``, + ), + ) + : Effect.die(`unexpected embedded UI path: ${path}`) }, - { "index.html": "/$bunfs/root/index.html" }, - ) - }).pipe(Effect.provide(AppFileSystem.defaultLayer), Effect.map(HttpServerResponse.toWeb)), - ) + }, + { "index.html": "/$bunfs/root/index.html" }, + ).pipe(Effect.map(HttpServerResponse.toWeb)) - const csp = response.headers.get("content-security-policy") ?? "" - expect(csp).toContain("script-src 'self' 'wasm-unsafe-eval'") - expect(csp).toContain(`'sha256-${createHash("sha256").update(script).digest("base64")}'`) - expect(csp).toContain("connect-src * data:") - }) + const csp = response.headers.get("content-security-policy") ?? "" + expect(csp).toContain("script-src 'self' 'wasm-unsafe-eval'") + expect(csp).toContain(`'sha256-${createHash("sha256").update(script).digest("base64")}'`) + expect(csp).toContain("connect-src * data:") + }), + ) - test("keeps matched API routes ahead of the UI fallback", async () => { - const response = await Server.Default().app.request("/session/ses_nope") + it.live("keeps matched API routes ahead of the UI fallback", () => + Effect.gen(function* () { + const response = yield* Effect.promise(() => Promise.resolve(Server.Default().app.request("/session/ses_nope"))) - expect(response.status).toBe(404) - }) + expect(response.status).toBe(404) + }), + ) - test("requires server password for the web UI", async () => { - Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true + it.live("requires server password for the web UI", () => + Effect.gen(function* () { + Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true - const response = await uiApp({ password: "secret", username: "opencode" }).request("/") + const response = yield* uiApp({ password: "secret", username: "opencode" }).request("/") - expect(response.status).toBe(401) - expect(response.headers.get("www-authenticate")).toBe('Basic realm="Secure Area"') - }) + expect(response.status).toBe(401) + expect(response.headers.get("www-authenticate")).toBe('Basic realm="Secure Area"') + }), + ) - test("accepts auth token for the web UI", async () => { - Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true + it.live("accepts auth token for the web UI", () => + Effect.gen(function* () { + Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true - const response = await uiApp({ - password: "secret", - username: "opencode", - client: httpClient(new Response("opencode", { headers: { "content-type": "text/html" } })), - }).request(`/?auth_token=${btoa("opencode:secret")}`) + const response = yield* uiApp({ + password: "secret", + username: "opencode", + client: httpClient(new Response("opencode", { headers: { "content-type": "text/html" } })), + }).request(`/?auth_token=${btoa("opencode:secret")}`) - expect(response.status).toBe(200) - expect(await response.text()).toBe("opencode") - }) + expect(response.status).toBe(200) + expect(yield* responseText(response)).toBe("opencode") + }), + ) - test("accepts basic auth for the web UI", async () => { - Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true + it.live("accepts basic auth for the web UI", () => + Effect.gen(function* () { + Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true - const response = await uiApp({ password: "secret", username: "opencode" }).request("/", { - headers: { authorization: `Basic ${btoa("opencode:secret")}` }, - }) + const response = yield* uiApp({ password: "secret", username: "opencode" }).request("/", { + headers: { authorization: `Basic ${btoa("opencode:secret")}` }, + }) - expect(response.status).toBe(200) - }) + expect(response.status).toBe(200) + }), + ) // Regression for #25698 (Ope): the browser fetches the PWA manifest and // its icons via flows that don't carry app-managed credentials (the // `` request is not under page-auth control), so the // server returning 401 breaks PWA install. These specific public assets // should bypass auth. - test("serves the PWA manifest without auth even when a server password is set", async () => { - Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true + it.live("serves the PWA manifest without auth even when a server password is set", () => + Effect.gen(function* () { + Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true + + for (const path of ["/site.webmanifest", "/web-app-manifest-192x192.png", "/web-app-manifest-512x512.png"]) { + const response = yield* uiApp({ + password: "secret", + username: "opencode", + client: httpClient(new Response("ok")), + }).request(path) + expect(response.status).not.toBe(401) + } + }), + ) - for (const path of ["/site.webmanifest", "/web-app-manifest-192x192.png", "/web-app-manifest-512x512.png"]) { - const response = await uiApp({ - password: "secret", - username: "opencode", - client: httpClient(new Response("ok")), - }).request(path) - expect(response.status).not.toBe(401) - } - }) - - test("allows web UI preflight without auth", async () => { - const response = await app({ password: "secret", username: "opencode" }).request("/", { - method: "OPTIONS", - headers: { - origin: "http://localhost:3000", - "access-control-request-method": "GET", - }, - }) - - expect(response.status).toBe(204) - expect(response.headers.get("access-control-allow-origin")).toBe("http://localhost:3000") - }) + it.live("allows web UI preflight without auth", () => + Effect.gen(function* () { + const response = yield* app({ password: "secret", username: "opencode" }).request("/", { + method: "OPTIONS", + headers: { + origin: "http://localhost:3000", + "access-control-request-method": "GET", + }, + }) + + expect(response.status).toBe(204) + expect(response.headers.get("access-control-allow-origin")).toBe("http://localhost:3000") + }), + ) }) From d88cef6adabf63f4ae628de4795afb76b3c46866 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 22:56:29 -0400 Subject: [PATCH 211/378] test(mcp): migrate headers tests to Effect runner (#27237) --- packages/opencode/test/mcp/headers.test.ts | 147 +++++++-------------- 1 file changed, 47 insertions(+), 100 deletions(-) diff --git a/packages/opencode/test/mcp/headers.test.ts b/packages/opencode/test/mcp/headers.test.ts index 5bc8f803d..c51ed00d3 100644 --- a/packages/opencode/test/mcp/headers.test.ts +++ b/packages/opencode/test/mcp/headers.test.ts @@ -1,6 +1,6 @@ -import { test, expect, mock, beforeEach } from "bun:test" +import { describe, expect, mock, beforeEach } from "bun:test" import { Effect } from "effect" -import type { MCP as MCPNS } from "../../src/mcp/index" +import { testEffect } from "../lib/effect" // Track what options were passed to each transport constructor const transportCalls: Array<{ @@ -46,53 +46,22 @@ beforeEach(() => { // Import MCP after mocking const { MCP } = await import("../../src/mcp/index") -const { AppRuntime } = await import("../../src/effect/app-runtime") -const { Instance } = await import("../../src/project/instance") -const { WithInstance } = await import("../../src/project/with-instance") -const { tmpdir } = await import("../fixture/fixture") -const service = MCP.Service as unknown as Effect.Effect - -test("headers are passed to transports when oauth is enabled (default)", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write( - `${dir}/opencode.json`, - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - mcp: { - "test-server": { - type: "remote", - url: "https://example.com/mcp", - headers: { - Authorization: "Bearer test-token", - "X-Custom-Header": "custom-value", - }, - }, +const it = testEffect(MCP.defaultLayer) + +describe("mcp.headers", () => { + it.instance("headers are passed to transports when oauth is enabled (default)", () => + Effect.gen(function* () { + const mcp = yield* MCP.Service + yield* mcp + .add("test-server", { + type: "remote", + url: "https://example.com/mcp", + headers: { + Authorization: "Bearer test-token", + "X-Custom-Header": "custom-value", }, - }), - ) - }, - }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - // Trigger MCP initialization - it will fail to connect but we can check the transport options - await AppRuntime.runPromise( - Effect.gen(function* () { - const mcp = yield* service - yield* mcp - .add("test-server", { - type: "remote", - url: "https://example.com/mcp", - headers: { - Authorization: "Bearer test-token", - "X-Custom-Header": "custom-value", - }, - }) - .pipe(Effect.catch(() => Effect.void)) - }), - ) + }) + .pipe(Effect.catch(() => Effect.void)) // Both transports should have been created with headers expect(transportCalls.length).toBeGreaterThanOrEqual(1) @@ -106,33 +75,22 @@ test("headers are passed to transports when oauth is enabled (default)", async ( // OAuth should be enabled by default, so authProvider should exist expect(call.options.authProvider).toBeDefined() } - }, - }) -}) - -test("headers are passed to transports when oauth is explicitly disabled", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - transportCalls.length = 0 - - await AppRuntime.runPromise( - Effect.gen(function* () { - const mcp = yield* service - yield* mcp - .add("test-server-no-oauth", { - type: "remote", - url: "https://example.com/mcp", - oauth: false, - headers: { - Authorization: "Bearer test-token", - }, - }) - .pipe(Effect.catch(() => Effect.void)) - }), - ) + }), + ) + + it.instance("headers are passed to transports when oauth is explicitly disabled", () => + Effect.gen(function* () { + const mcp = yield* MCP.Service + yield* mcp + .add("test-server-no-oauth", { + type: "remote", + url: "https://example.com/mcp", + oauth: false, + headers: { + Authorization: "Bearer test-token", + }, + }) + .pipe(Effect.catch(() => Effect.void)) expect(transportCalls.length).toBeGreaterThanOrEqual(1) @@ -144,29 +102,18 @@ test("headers are passed to transports when oauth is explicitly disabled", async // OAuth is disabled, so no authProvider expect(call.options.authProvider).toBeUndefined() } - }, - }) -}) - -test("no requestInit when headers are not provided", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - transportCalls.length = 0 - - await AppRuntime.runPromise( - Effect.gen(function* () { - const mcp = yield* service - yield* mcp - .add("test-server-no-headers", { - type: "remote", - url: "https://example.com/mcp", - }) - .pipe(Effect.catch(() => Effect.void)) - }), - ) + }), + ) + + it.instance("no requestInit when headers are not provided", () => + Effect.gen(function* () { + const mcp = yield* MCP.Service + yield* mcp + .add("test-server-no-headers", { + type: "remote", + url: "https://example.com/mcp", + }) + .pipe(Effect.catch(() => Effect.void)) expect(transportCalls.length).toBeGreaterThanOrEqual(1) @@ -174,6 +121,6 @@ test("no requestInit when headers are not provided", async () => { // No headers means requestInit should be undefined expect(call.options.requestInit).toBeUndefined() } - }, - }) + }), + ) }) From 6d3b2fe08bdb17fc265c44d542a9fc6ca38e150d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 23:01:29 -0400 Subject: [PATCH 212/378] test(server): stabilize SDK project skill prompt test (#27239) --- packages/opencode/test/server/httpapi-sdk.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/opencode/test/server/httpapi-sdk.test.ts b/packages/opencode/test/server/httpapi-sdk.test.ts index 2f5d81342..a76877a0b 100644 --- a/packages/opencode/test/server/httpapi-sdk.test.ts +++ b/packages/opencode/test/server/httpapi-sdk.test.ts @@ -743,7 +743,7 @@ describe("HttpApi SDK", () => { ) httpapi( - "includes project skills in REST API async prompt context", + "includes project skills in REST API prompt context", withFakeLlmProject("default", { setup: writeProjectSkill }, ({ sdk, llm }) => Effect.gen(function* () { yield* llm.text("skill context ok", { usage: { input: 11, output: 7 } }) @@ -755,18 +755,17 @@ describe("HttpApi SDK", () => { ) const sessionID = String(record(session.data).id) const prompt = yield* capture(() => - sdk.session.promptAsync({ + sdk.session.prompt({ sessionID, agent: "build", model: { providerID: "test", modelID: "test-model" }, parts: [{ type: "text", text: "hello skill context" }], }), ) - yield* llm.wait(1) const inputs = yield* llm.inputs expect(session.status).toBe(200) - expect(prompt.status).toBe(204) + expect(prompt.status).toBe(200) expect(JSON.stringify(inputs[0])).toContain("project-rest-skill") }), ), From 68c4951318b3ace5943eb525e225d316aa3803ed Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 23:05:30 -0400 Subject: [PATCH 213/378] test(mcp): migrate lifecycle tests to Effect runner (#27205) --- packages/opencode/test/mcp/lifecycle.test.ts | 752 ++++++++++--------- 1 file changed, 390 insertions(+), 362 deletions(-) diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts index 5afc85e3b..185086fe6 100644 --- a/packages/opencode/test/mcp/lifecycle.test.ts +++ b/packages/opencode/test/mcp/lifecycle.test.ts @@ -1,7 +1,7 @@ -import { test, expect, mock, beforeEach } from "bun:test" -import { InstanceRuntime } from "../../src/project/instance-runtime" -import { Effect } from "effect" +import { expect, mock, beforeEach } from "bun:test" +import { Effect, Exit } from "effect" import type { MCP as MCPNS } from "../../src/mcp/index" +import { testEffect } from "../lib/effect" // --- Mock infrastructure --- @@ -179,39 +179,9 @@ beforeEach(() => { // Import after mocks const { MCP } = await import("../../src/mcp/index") -const { Instance } = await import("../../src/project/instance") -const { WithInstance } = await import("../../src/project/with-instance") -const { tmpdir } = await import("../fixture/fixture") - -// --- Helper --- - -function withInstance( - config: Record, - fn: (mcp: MCPNS.Interface) => Effect.Effect, -) { - return async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write( - `${dir}/opencode.json`, - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - mcp: config, - }), - ) - }, - }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await Effect.runPromise(MCP.Service.use(fn).pipe(Effect.provide(MCP.defaultLayer))) - // dispose instance to clean up state between tests - await InstanceRuntime.disposeInstance(Instance.current) - }, - }) - } -} +const { McpOAuthCallback } = await import("../../src/mcp/oauth-callback") + +const it = testEffect(MCP.defaultLayer) function statusName(status: Record | MCPNS.Status, server: string) { if ("status" in status) return status.status @@ -222,82 +192,82 @@ function statusName(status: Record | MCPNS.Status, server: // Test: tools() are cached after connect // ======================================================================== -test( +it.instance( "tools() reuses cached tool definitions after connect", - withInstance({}, (mcp) => - Effect.gen(function* () { - lastCreatedClientName = "my-server" - const serverState = getOrCreateClientState("my-server") - serverState.tools = [ - { name: "do_thing", description: "does a thing", inputSchema: { type: "object", properties: {} } }, - ] - - // First: add the server successfully - const addResult = yield* mcp.add("my-server", { - type: "local", - command: ["echo", "test"], - }) - expect((addResult.status as any)["my-server"]?.status ?? (addResult.status as any).status).toBe("connected") - - expect(serverState.listToolsCalls).toBe(1) - - const toolsA = yield* mcp.tools() - const toolsB = yield* mcp.tools() - expect(Object.keys(toolsA).length).toBeGreaterThan(0) - expect(Object.keys(toolsB).length).toBeGreaterThan(0) - expect(serverState.listToolsCalls).toBe(1) - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "my-server" + const serverState = getOrCreateClientState("my-server") + serverState.tools = [ + { name: "do_thing", description: "does a thing", inputSchema: { type: "object", properties: {} } }, + ] + + // First: add the server successfully + const addResult = yield* mcp.add("my-server", { + type: "local", + command: ["echo", "test"], + }) + expect((addResult.status as any)["my-server"]?.status ?? (addResult.status as any).status).toBe("connected") + + expect(serverState.listToolsCalls).toBe(1) + + const toolsA = yield* mcp.tools() + const toolsB = yield* mcp.tools() + expect(Object.keys(toolsA).length).toBeGreaterThan(0) + expect(Object.keys(toolsB).length).toBeGreaterThan(0) + expect(serverState.listToolsCalls).toBe(1) + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: tool change notifications refresh the cache // ======================================================================== -test( +it.instance( "tool change notifications refresh cached tool definitions", - withInstance({}, (mcp) => - Effect.gen(function* () { - lastCreatedClientName = "status-server" - const serverState = getOrCreateClientState("status-server") - - yield* mcp.add("status-server", { - type: "local", - command: ["echo", "test"], - }) - - const before = yield* mcp.tools() - expect(Object.keys(before).some((key) => key.includes("test_tool"))).toBe(true) - expect(serverState.listToolsCalls).toBe(1) - - serverState.tools = [{ name: "next_tool", description: "next", inputSchema: { type: "object", properties: {} } }] - - const handler = Array.from(serverState.notificationHandlers.values())[0] - expect(handler).toBeDefined() - yield* Effect.promise(() => handler?.()) - - const after = yield* mcp.tools() - expect(Object.keys(after).some((key) => key.includes("next_tool"))).toBe(true) - expect(Object.keys(after).some((key) => key.includes("test_tool"))).toBe(false) - expect(serverState.listToolsCalls).toBe(2) - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "status-server" + const serverState = getOrCreateClientState("status-server") + + yield* mcp.add("status-server", { + type: "local", + command: ["echo", "test"], + }) + + const before = yield* mcp.tools() + expect(Object.keys(before).some((key) => key.includes("test_tool"))).toBe(true) + expect(serverState.listToolsCalls).toBe(1) + + serverState.tools = [ + { name: "next_tool", description: "next", inputSchema: { type: "object", properties: {} } }, + ] + + const handler = Array.from(serverState.notificationHandlers.values())[0] + expect(handler).toBeDefined() + yield* Effect.promise(() => handler?.()) + + const after = yield* mcp.tools() + expect(Object.keys(after).some((key) => key.includes("next_tool"))).toBe(true) + expect(Object.keys(after).some((key) => key.includes("test_tool"))).toBe(false) + expect(serverState.listToolsCalls).toBe(2) + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: connect() / disconnect() lifecycle // ======================================================================== -test( +it.instance( "disconnect sets status to disabled and removes client", - withInstance( - { - "disc-server": { - type: "local", - command: ["echo", "test"], - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { lastCreatedClientName = "disc-server" getOrCreateClientState("disc-server") @@ -315,24 +285,27 @@ test( const statusAfter = yield* mcp.status() expect(statusAfter["disc-server"]?.status).toBe("disabled") - // Tools should be empty after disconnect const tools = yield* mcp.tools() const serverTools = Object.keys(tools).filter((k) => k.startsWith("disc-server")) expect(serverTools.length).toBe(0) }), - ), + ), + { + config: { + mcp: { + "disc-server": { + type: "local", + command: ["echo", "test"], + }, + }, + }, + }, ) -test( +it.instance( "connect() after disconnect() re-establishes the server", - withInstance( - { - "reconn-server": { - type: "local", - command: ["echo", "test"], - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { lastCreatedClientName = "reconn-server" const serverState = getOrCreateClientState("reconn-server") @@ -348,70 +321,71 @@ test( yield* mcp.disconnect("reconn-server") expect((yield* mcp.status())["reconn-server"]?.status).toBe("disabled") - // Reconnect yield* mcp.connect("reconn-server") expect((yield* mcp.status())["reconn-server"]?.status).toBe("connected") const tools = yield* mcp.tools() expect(Object.keys(tools).some((k) => k.includes("my_tool"))).toBe(true) }), - ), + ), + { + config: { + mcp: { + "reconn-server": { + type: "local", + command: ["echo", "test"], + }, + }, + }, + }, ) // ======================================================================== // Test: add() closes existing client before replacing // ======================================================================== -test( +it.instance( "add() closes the old client when replacing a server", // Don't put the server in config — add it dynamically so we control // exactly which client instance is "first" vs "second". - withInstance({}, (mcp) => - Effect.gen(function* () { - lastCreatedClientName = "replace-server" - const firstState = getOrCreateClientState("replace-server") - - yield* mcp.add("replace-server", { - type: "local", - command: ["echo", "test"], - }) - - expect(firstState.closed).toBe(false) - - // Create new state for second client - clientStates.delete("replace-server") - const secondState = getOrCreateClientState("replace-server") - - // Re-add should close the first client - yield* mcp.add("replace-server", { - type: "local", - command: ["echo", "test"], - }) - - expect(firstState.closed).toBe(true) - expect(secondState.closed).toBe(false) - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "replace-server" + const firstState = getOrCreateClientState("replace-server") + + yield* mcp.add("replace-server", { + type: "local", + command: ["echo", "test"], + }) + + expect(firstState.closed).toBe(false) + + // Create new state for second client + clientStates.delete("replace-server") + const secondState = getOrCreateClientState("replace-server") + + // Re-add should close the first client + yield* mcp.add("replace-server", { + type: "local", + command: ["echo", "test"], + }) + + expect(firstState.closed).toBe(true) + expect(secondState.closed).toBe(false) + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: state init with mixed success/failure // ======================================================================== -test( +it.instance( "init connects available servers even when one fails", - withInstance( - { - "good-server": { - type: "local", - command: ["echo", "good"], - }, - "bad-server": { - type: "local", - command: ["echo", "bad"], - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { // Set up good server const goodState = getOrCreateClientState("good-server") @@ -443,77 +417,88 @@ test( const tools = yield* mcp.tools() expect(Object.keys(tools).some((k) => k.includes("good_tool"))).toBe(true) }), - ), + ), + { + config: { + mcp: { + "good-server": { + type: "local", + command: ["echo", "good"], + }, + "bad-server": { + type: "local", + command: ["echo", "bad"], + }, + }, + }, + }, ) -test( +it.instance( "falls back when MCP output schema refs fail SDK tool discovery", - withInstance({}, (mcp) => - Effect.gen(function* () { - lastCreatedClientName = "stitch-like-server" - const serverState = getOrCreateClientState("stitch-like-server") - serverState.listToolsShouldFail = true - serverState.listToolsError = "can't resolve reference #/$defs/ScreenInstance from id #" - serverState.tools = [ - { - name: "render_screen", - description: "renders a screen", - inputSchema: { type: "object", properties: { prompt: { type: "string" } }, required: ["prompt"] }, - outputSchema: { type: "object", properties: { screen: { $ref: "#/$defs/ScreenInstance" } } }, - }, - ] + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "stitch-like-server" + const serverState = getOrCreateClientState("stitch-like-server") + serverState.listToolsShouldFail = true + serverState.listToolsError = "can't resolve reference #/$defs/ScreenInstance from id #" + serverState.tools = [ + { + name: "render_screen", + description: "renders a screen", + inputSchema: { type: "object", properties: { prompt: { type: "string" } }, required: ["prompt"] }, + outputSchema: { type: "object", properties: { screen: { $ref: "#/$defs/ScreenInstance" } } }, + }, + ] - const addResult = yield* mcp.add("stitch-like-server", { - type: "local", - command: ["echo", "test"], - }) + const addResult = yield* mcp.add("stitch-like-server", { + type: "local", + command: ["echo", "test"], + }) - expect(statusName(addResult.status, "stitch-like-server")).toBe("connected") + expect(statusName(addResult.status, "stitch-like-server")).toBe("connected") - const tools = yield* mcp.tools() - expect(Object.keys(tools).some((key) => key.includes("render_screen"))).toBe(true) - expect(serverState.listToolsCalls).toBe(1) - expect(serverState.requestCalls).toBe(1) - }), - ), + const tools = yield* mcp.tools() + expect(Object.keys(tools).some((key) => key.includes("render_screen"))).toBe(true) + expect(serverState.listToolsCalls).toBe(1) + expect(serverState.requestCalls).toBe(1) + }), + ), + { config: { mcp: {} } }, ) -test( +it.instance( "does not fall back for non-schema MCP tool discovery errors", - withInstance({}, (mcp) => - Effect.gen(function* () { - lastCreatedClientName = "broken-server" - const serverState = getOrCreateClientState("broken-server") - serverState.listToolsShouldFail = true - serverState.listToolsError = "transport closed" - - const addResult = yield* mcp.add("broken-server", { - type: "local", - command: ["echo", "test"], - }) - - expect(statusName(addResult.status, "broken-server")).toBe("failed") - expect(serverState.listToolsCalls).toBe(1) - expect(serverState.requestCalls).toBe(0) - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "broken-server" + const serverState = getOrCreateClientState("broken-server") + serverState.listToolsShouldFail = true + serverState.listToolsError = "transport closed" + + const addResult = yield* mcp.add("broken-server", { + type: "local", + command: ["echo", "test"], + }) + + expect(statusName(addResult.status, "broken-server")).toBe("failed") + expect(serverState.listToolsCalls).toBe(1) + expect(serverState.requestCalls).toBe(0) + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: disabled server via config // ======================================================================== -test( +it.instance( "disabled server is marked as disabled without attempting connection", - withInstance( - { - "disabled-server": { - type: "local", - command: ["echo", "test"], - enabled: false, - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { const countBefore = clientCreateCount @@ -529,23 +514,28 @@ test( const status = yield* mcp.status() expect(status["disabled-server"]?.status).toBe("disabled") }), - ), + ), + { + config: { + mcp: { + "disabled-server": { + type: "local", + command: ["echo", "test"], + enabled: false, + }, + }, + }, + }, ) // ======================================================================== // Test: prompts() and resources() // ======================================================================== -test( +it.instance( "prompts() returns prompts from connected servers", - withInstance( - { - "prompt-server": { - type: "local", - command: ["echo", "test"], - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { lastCreatedClientName = "prompt-server" const serverState = getOrCreateClientState("prompt-server") @@ -562,19 +552,23 @@ test( expect(key).toContain("prompt-server") expect(key).toContain("my-prompt") }), - ), + ), + { + config: { + mcp: { + "prompt-server": { + type: "local", + command: ["echo", "test"], + }, + }, + }, + }, ) -test( +it.instance( "resources() returns resources from connected servers", - withInstance( - { - "resource-server": { - type: "local", - command: ["echo", "test"], - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { lastCreatedClientName = "resource-server" const serverState = getOrCreateClientState("resource-server") @@ -591,19 +585,23 @@ test( expect(key).toContain("resource-server") expect(key).toContain("my-resource") }), - ), + ), + { + config: { + mcp: { + "resource-server": { + type: "local", + command: ["echo", "test"], + }, + }, + }, + }, ) -test( +it.instance( "prompts() skips disconnected servers", - withInstance( - { - "prompt-disc-server": { - type: "local", - command: ["echo", "test"], - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { lastCreatedClientName = "prompt-disc-server" const serverState = getOrCreateClientState("prompt-disc-server") @@ -619,67 +617,77 @@ test( const prompts = yield* mcp.prompts() expect(Object.keys(prompts).length).toBe(0) }), - ), + ), + { + config: { + mcp: { + "prompt-disc-server": { + type: "local", + command: ["echo", "test"], + }, + }, + }, + }, ) // ======================================================================== // Test: connect() on nonexistent server // ======================================================================== -test( +it.instance( "connect() on nonexistent server does not throw", - withInstance({}, (mcp) => - Effect.gen(function* () { - // Should not throw - yield* mcp.connect("nonexistent") - const status = yield* mcp.status() - expect(status["nonexistent"]).toBeUndefined() - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + // Should not throw + yield* mcp.connect("nonexistent") + const status = yield* mcp.status() + expect(status["nonexistent"]).toBeUndefined() + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: disconnect() on nonexistent server // ======================================================================== -test( +it.instance( "disconnect() on nonexistent server does not throw", - withInstance({}, (mcp) => - Effect.gen(function* () { - yield* mcp.disconnect("nonexistent") - // Should complete without error - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + yield* mcp.disconnect("nonexistent") + // Should complete without error + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: tools() with no MCP servers configured // ======================================================================== -test( +it.instance( "tools() returns empty when no MCP servers are configured", - withInstance({}, (mcp) => - Effect.gen(function* () { - const tools = yield* mcp.tools() - expect(Object.keys(tools).length).toBe(0) - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + const tools = yield* mcp.tools() + expect(Object.keys(tools).length).toBe(0) + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: connect failure during create() // ======================================================================== -test( +it.instance( "server that fails to connect is marked as failed", - withInstance( - { - "fail-connect": { - type: "local", - command: ["echo", "test"], - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { lastCreatedClientName = "fail-connect" getOrCreateClientState("fail-connect") @@ -701,51 +709,55 @@ test( const tools = yield* mcp.tools() expect(Object.keys(tools).length).toBe(0) }), - ), + ), + { + config: { + mcp: { + "fail-connect": { + type: "local", + command: ["echo", "test"], + }, + }, + }, + }, ) // ======================================================================== // Bug #5: McpOAuthCallback.cancelPending uses wrong key // ======================================================================== -test("McpOAuthCallback.cancelPending is keyed by mcpName but pendingAuths uses oauthState", async () => { - const { McpOAuthCallback } = await import("../../src/mcp/oauth-callback") - - // Register a pending auth with an oauthState key, associated to an mcpName - const oauthState = "abc123hexstate" - const callbackPromise = McpOAuthCallback.waitForCallback(oauthState, "my-mcp-server") - - // cancelPending is called with mcpName — should find the entry via reverse index - McpOAuthCallback.cancelPending("my-mcp-server") - - // The callback should still be pending because cancelPending looked up - // "my-mcp-server" in a map keyed by "abc123hexstate" - let rejected = false - callbackPromise.then(() => {}).catch(() => (rejected = true)) - - // Give it a tick - await new Promise((r) => setTimeout(r, 50)) - - // cancelPending("my-mcp-server") should have rejected the pending callback - expect(rejected).toBe(true) +it.live("McpOAuthCallback.cancelPending is keyed by mcpName but pendingAuths uses oauthState", () => + Effect.acquireUseRelease( + Effect.sync(() => McpOAuthCallback.waitForCallback("abc123hexstate", "my-mcp-server")), + (callback) => + Effect.gen(function* () { + McpOAuthCallback.cancelPending("my-mcp-server") + + const exit = yield* Effect.tryPromise({ + try: () => callback, + catch: (error) => (error instanceof Error ? error : new Error(String(error))), + }).pipe( + Effect.timeoutOrElse({ + duration: "1 second", + orElse: () => Effect.fail(new Error("timed out waiting for OAuth cancellation")), + }), + Effect.exit, + ) - await McpOAuthCallback.stop() -}) + expect(Exit.isFailure(exit)).toBe(true) + }), + () => Effect.promise(() => McpOAuthCallback.stop()).pipe(Effect.ignore), + ), +) // ======================================================================== // Test: multiple tools from same server get correct name prefixes // ======================================================================== -test( +it.instance( "tools() prefixes tool names with sanitized server name", - withInstance( - { - "my.special-server": { - type: "local", - command: ["echo", "test"], - }, - }, - (mcp) => + () => + MCP.Service.use((mcp: MCPNS.Interface) => Effect.gen(function* () { lastCreatedClientName = "my.special-server" const serverState = getOrCreateClientState("my.special-server") @@ -768,87 +780,103 @@ test( expect(keys.some((k) => k.endsWith("tool_b"))).toBe(true) expect(keys.length).toBe(2) }), - ), + ), + { + config: { + mcp: { + "my.special-server": { + type: "local", + command: ["echo", "test"], + }, + }, + }, + }, ) // ======================================================================== // Test: transport leak — local stdio timeout (#19168) // ======================================================================== -test( +it.instance( "local stdio transport is closed when connect times out (no process leak)", - withInstance({}, (mcp) => - Effect.gen(function* () { - lastCreatedClientName = "hanging-server" - getOrCreateClientState("hanging-server") - connectShouldHang = true - - const addResult = yield* mcp.add("hanging-server", { - type: "local", - command: ["node", "fake.js"], - timeout: 100, - }) - - const serverStatus = (addResult.status as any)["hanging-server"] ?? addResult.status - expect(serverStatus.status).toBe("failed") - expect(serverStatus.error).toContain("timed out") - // Transport must be closed to avoid orphaned child process - expect(transportCloseCount).toBeGreaterThanOrEqual(1) - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "hanging-server" + getOrCreateClientState("hanging-server") + connectShouldHang = true + + const addResult = yield* mcp.add("hanging-server", { + type: "local", + command: ["node", "fake.js"], + timeout: 100, + }) + + const serverStatus = (addResult.status as any)["hanging-server"] ?? addResult.status + expect(serverStatus.status).toBe("failed") + expect(serverStatus.error).toContain("timed out") + // Transport must be closed to avoid orphaned child process + expect(transportCloseCount).toBeGreaterThanOrEqual(1) + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: transport leak — remote timeout (#19168) // ======================================================================== -test( +it.instance( "remote transport is closed when connect times out", - withInstance({}, (mcp) => - Effect.gen(function* () { - lastCreatedClientName = "hanging-remote" - getOrCreateClientState("hanging-remote") - connectShouldHang = true - - const addResult = yield* mcp.add("hanging-remote", { - type: "remote", - url: "http://localhost:9999/mcp", - timeout: 100, - oauth: false, - }) - - const serverStatus = (addResult.status as any)["hanging-remote"] ?? addResult.status - expect(serverStatus.status).toBe("failed") - // Transport must be closed to avoid leaked HTTP connections - expect(transportCloseCount).toBeGreaterThanOrEqual(1) - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "hanging-remote" + getOrCreateClientState("hanging-remote") + connectShouldHang = true + + const addResult = yield* mcp.add("hanging-remote", { + type: "remote", + url: "http://localhost:9999/mcp", + timeout: 100, + oauth: false, + }) + + const serverStatus = (addResult.status as any)["hanging-remote"] ?? addResult.status + expect(serverStatus.status).toBe("failed") + // Transport must be closed to avoid leaked HTTP connections + expect(transportCloseCount).toBeGreaterThanOrEqual(1) + }), + ), + { config: { mcp: {} } }, ) // ======================================================================== // Test: transport leak — failed remote transports not closed (#19168) // ======================================================================== -test( +it.instance( "failed remote transport is closed before trying next transport", - withInstance({}, (mcp) => - Effect.gen(function* () { - lastCreatedClientName = "fail-remote" - getOrCreateClientState("fail-remote") - connectShouldFail = true - connectError = "Connection refused" - - const addResult = yield* mcp.add("fail-remote", { - type: "remote", - url: "http://localhost:9999/mcp", - timeout: 5000, - oauth: false, - }) - - const serverStatus = (addResult.status as any)["fail-remote"] ?? addResult.status - expect(serverStatus.status).toBe("failed") - // Both StreamableHTTP and SSE transports should be closed - expect(transportCloseCount).toBeGreaterThanOrEqual(2) - }), - ), + () => + MCP.Service.use((mcp: MCPNS.Interface) => + Effect.gen(function* () { + lastCreatedClientName = "fail-remote" + getOrCreateClientState("fail-remote") + connectShouldFail = true + connectError = "Connection refused" + + const addResult = yield* mcp.add("fail-remote", { + type: "remote", + url: "http://localhost:9999/mcp", + timeout: 5000, + oauth: false, + }) + + const serverStatus = (addResult.status as any)["fail-remote"] ?? addResult.status + expect(serverStatus.status).toBe("failed") + // Both StreamableHTTP and SSE transports should be closed + expect(transportCloseCount).toBeGreaterThanOrEqual(2) + }), + ), + { config: { mcp: {} } }, ) From 913ea36ae61617152ae28a092e0c4f9446dc09a2 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 23:13:05 -0400 Subject: [PATCH 214/378] test(server): scope MCP HttpApi handler (#27226) --- .../opencode/test/server/httpapi-mcp.test.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/opencode/test/server/httpapi-mcp.test.ts b/packages/opencode/test/server/httpapi-mcp.test.ts index 136a17d40..be11a431e 100644 --- a/packages/opencode/test/server/httpapi-mcp.test.ts +++ b/packages/opencode/test/server/httpapi-mcp.test.ts @@ -23,13 +23,19 @@ function app() { return Server.Default().app } type TestApp = ReturnType +type TestHandler = ReturnType -const request = Effect.fnUntraced(function* (route: string, directory: string, init?: RequestInit) { +const handlerScoped = Effect.acquireRelease( + Effect.sync(() => ExperimentalHttpApiServer.webHandler()), + (handler) => Effect.promise(() => handler.dispose()).pipe(Effect.ignore), +) + +const request = Effect.fnUntraced(function* (handler: TestHandler, route: string, directory: string, init?: RequestInit) { const headers = new Headers(init?.headers) headers.set("x-opencode-directory", directory) return yield* Effect.promise(() => Promise.resolve( - ExperimentalHttpApiServer.webHandler().handler( + handler.handler( new Request(`http://localhost${route}`, { ...init, headers, @@ -58,7 +64,8 @@ describe("mcp HttpApi", () => { () => Effect.gen(function* () { const tmp = yield* TestInstance - const response = yield* request(McpPaths.status, tmp.directory) + const handler = yield* handlerScoped + const response = yield* request(handler, McpPaths.status, tmp.directory) expect(response.status).toBe(200) expect(yield* json(response)).toEqual({ demo: { status: "disabled" } }) @@ -81,7 +88,8 @@ describe("mcp HttpApi", () => { () => Effect.gen(function* () { const tmp = yield* TestInstance - const added = yield* request(McpPaths.status, tmp.directory, { + const handler = yield* handlerScoped + const added = yield* request(handler, McpPaths.status, tmp.directory, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ @@ -96,11 +104,11 @@ describe("mcp HttpApi", () => { expect(added.status).toBe(200) expect(yield* json(added)).toMatchObject({ added: { status: "disabled" } }) - const connected = yield* request("/mcp/demo/connect", tmp.directory, { method: "POST" }) + const connected = yield* request(handler, "/mcp/demo/connect", tmp.directory, { method: "POST" }) expect(connected.status).toBe(200) expect(yield* json(connected)).toBe(true) - const disconnected = yield* request("/mcp/demo/disconnect", tmp.directory, { method: "POST" }) + const disconnected = yield* request(handler, "/mcp/demo/disconnect", tmp.directory, { method: "POST" }) expect(disconnected.status).toBe(200) expect(yield* json(disconnected)).toBe(true) }), @@ -122,13 +130,14 @@ describe("mcp HttpApi", () => { () => Effect.gen(function* () { const tmp = yield* TestInstance - const start = yield* request("/mcp/demo/auth", tmp.directory, { method: "POST" }) + const handler = yield* handlerScoped + const start = yield* request(handler, "/mcp/demo/auth", tmp.directory, { method: "POST" }) expect(start.status).toBe(400) - const authenticate = yield* request("/mcp/demo/auth/authenticate", tmp.directory, { method: "POST" }) + const authenticate = yield* request(handler, "/mcp/demo/auth/authenticate", tmp.directory, { method: "POST" }) expect(authenticate.status).toBe(400) - const removed = yield* request("/mcp/demo/auth", tmp.directory, { method: "DELETE" }) + const removed = yield* request(handler, "/mcp/demo/auth", tmp.directory, { method: "DELETE" }) expect(removed.status).toBe(200) expect(yield* json(removed)).toEqual({ success: true }) }), From 784796e27a03d1afd8231f2c6f717ca4862ca4f1 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 03:14:19 +0000 Subject: [PATCH 215/378] chore: generate --- packages/opencode/test/server/httpapi-mcp.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/server/httpapi-mcp.test.ts b/packages/opencode/test/server/httpapi-mcp.test.ts index be11a431e..21cb0cfd2 100644 --- a/packages/opencode/test/server/httpapi-mcp.test.ts +++ b/packages/opencode/test/server/httpapi-mcp.test.ts @@ -30,7 +30,12 @@ const handlerScoped = Effect.acquireRelease( (handler) => Effect.promise(() => handler.dispose()).pipe(Effect.ignore), ) -const request = Effect.fnUntraced(function* (handler: TestHandler, route: string, directory: string, init?: RequestInit) { +const request = Effect.fnUntraced(function* ( + handler: TestHandler, + route: string, + directory: string, + init?: RequestInit, +) { const headers = new Headers(init?.headers) headers.set("x-opencode-directory", directory) return yield* Effect.promise(() => From 77e51b0a32cf5458d6c50a4fb487b085142b748d Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 12 May 2026 23:19:23 -0400 Subject: [PATCH 216/378] zen: stat worker --- infra/console.ts | 1 + .../app/src/routes/zen/util/handler.ts | 3 ++- .../src/routes/zen/util/modelTpsLimiter.ts | 16 +++++++++++----- packages/console/function/src/stat.ts | 19 ++++++++++--------- sst-env.d.ts | 1 + sst.config.ts | 2 +- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/infra/console.ts b/infra/console.ts index c247dfcdb..56befe626 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -301,4 +301,5 @@ new sst.cloudflare.x.SolidStart("Console", { export const stat = new sst.cloudflare.Worker("Stat", { handler: "packages/console/function/src/stat.ts", link: [database], + url: true, }) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 540dfe7e8..a550a6e4c 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -320,6 +320,7 @@ export async function handler( await modelTpsLimiter?.track( providerInfo.id, providerInfo.model, + providerInfo.tpsGoal, timestampFirstByte, timestampLastByte, usageInfo, @@ -525,7 +526,7 @@ export async function handler( }) .filter((provider) => { if (!provider.tpsGoal) return true - const isLowTps = modelTpsLimits?.[`${provider.id}/${provider.model}`] ?? false + const isLowTps = modelTpsLimits?.[`${provider.id}/${provider.model}/${provider.tpsGoal}`] ?? false return !isLowTps }) .map((provider) => { diff --git a/packages/console/app/src/routes/zen/util/modelTpsLimiter.ts b/packages/console/app/src/routes/zen/util/modelTpsLimiter.ts index 428272eec..477d08ce6 100644 --- a/packages/console/app/src/routes/zen/util/modelTpsLimiter.ts +++ b/packages/console/app/src/routes/zen/util/modelTpsLimiter.ts @@ -5,7 +5,7 @@ import { UsageInfo } from "./provider/provider" export function createModelTpsLimiter(providers: { id: string; model: string; tpsGoal?: number }[]) { const tpsGoals = Object.fromEntries( providers.flatMap((p) => { - return p.tpsGoal ? [[`${p.id}/${p.model}`, p.tpsGoal]] : [] + return p.tpsGoal ? [[`${p.id}/${p.model}/${p.tpsGoal}`, p.tpsGoal]] : [] }), ) const ids = Object.keys(tpsGoals) @@ -56,11 +56,17 @@ export function createModelTpsLimiter(providers: { id: string; model: string; tp }), ) }, - track: async (provider: string, model: string, tsFirstByte: number, tsLastByte: number, usageInfo: UsageInfo) => { - const id = `${provider}/${model}` - if (!ids.includes(id)) return - const tpsGoal = tpsGoals[id] + track: async ( + provider: string, + model: string, + tpsGoal: number | undefined, + tsFirstByte: number, + tsLastByte: number, + usageInfo: UsageInfo, + ) => { if (!tpsGoal) return + const id = `${provider}/${model}/${tpsGoal}` + if (!ids.includes(id)) return if (tsFirstByte <= 0 || tsLastByte <= 0) return const tokens = usageInfo.outputTokens if (tokens <= 10) return diff --git a/packages/console/function/src/stat.ts b/packages/console/function/src/stat.ts index 078aabb51..9a1a1cc14 100644 --- a/packages/console/function/src/stat.ts +++ b/packages/console/function/src/stat.ts @@ -1,16 +1,17 @@ -import { WorkerEntrypoint } from "cloudflare:workers" import { and, Database, inArray } from "@opencode-ai/console-core/drizzle/index.js" import { ModelTpsRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js" +type Entry = { provider: string; model: string; tps: number } type Result = Record -export default class Stat extends WorkerEntrypoint { - async fetch() { - return new Response("Not Found", { status: 404 }) - } +export default { + async fetch(request: Request) { + if (request.method !== "POST") return new Response("Method Not Allowed", { status: 405 }) - async getStats(ids: string[]): Promise { - if (ids.length === 0) return {} + const entries = (await request.json()) as Entry[] + if (!Array.isArray(entries) || entries.length === 0) return Response.json({} satisfies Result) + + const ids = entries.map((e) => `${e.provider}/${e.model}/${e.tps}`) const toInterval = (date: Date) => parseInt( @@ -34,6 +35,6 @@ export default class Stat extends WorkerEntrypoint { result[row.id].qualify += row.qualify result[row.id].unqualify += row.unqualify } - return result - } + return Response.json(result) + }, } diff --git a/sst-env.d.ts b/sst-env.d.ts index a55dd4bb4..b75b4bd6e 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -155,6 +155,7 @@ declare module "sst" { } "Stat": { "type": "sst.cloudflare.Worker" + "url": string } "Teams": { "type": "sst.cloudflare.SolidStart" diff --git a/sst.config.ts b/sst.config.ts index 2feb3ac24..4bf7cc6a8 100644 --- a/sst.config.ts +++ b/sst.config.ts @@ -26,7 +26,7 @@ export default $config({ } return { - STAT_WORKER_NAME: stat.nodes.worker.scriptName, + StatWorkerUrl: stat.url, } }, }) From 8249baeb4ecded9f1f2ef6bd5c5d1f0907e984ed Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 23:20:20 -0400 Subject: [PATCH 217/378] test(pty): migrate shell tests to Effect runner (#27238) --- packages/opencode/test/pty/pty-shell.test.ts | 112 +++++++------------ 1 file changed, 41 insertions(+), 71 deletions(-) diff --git a/packages/opencode/test/pty/pty-shell.test.ts b/packages/opencode/test/pty/pty-shell.test.ts index 00e965d25..e8132dec7 100644 --- a/packages/opencode/test/pty/pty-shell.test.ts +++ b/packages/opencode/test/pty/pty-shell.test.ts @@ -1,39 +1,35 @@ -import { describe, expect, test } from "bun:test" -import { AppRuntime } from "../../src/effect/app-runtime" +import { describe, expect } from "bun:test" import { Effect } from "effect" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { Pty } from "../../src/pty" import { Shell } from "../../src/shell/shell" -import { tmpdir } from "../fixture/fixture" +import { testEffect } from "../lib/effect" Shell.preferred.reset() +const it = testEffect(Pty.defaultLayer) + +const createPty = (input: Pty.CreateInput) => + Effect.acquireRelease( + Effect.gen(function* () { + const pty = yield* Pty.Service + const info = yield* pty.create(input) + return { pty, info } + }), + ({ pty, info }) => pty.remove(info.id).pipe(Effect.ignore), + ).pipe(Effect.map(({ info }) => info)) + describe("pty shell args", () => { if (process.platform !== "win32") return const ps = Bun.which("pwsh") || Bun.which("powershell") if (ps) { - test( + it.instance( "does not add login args to pwsh", - async () => { - await using dir = await tmpdir() - await WithInstance.provide({ - directory: dir.path, - fn: () => - AppRuntime.runPromise( - Effect.gen(function* () { - const pty = yield* Pty.Service - const info = yield* pty.create({ command: ps, title: "pwsh" }) - try { - expect(info.args).toEqual([]) - } finally { - yield* pty.remove(info.id) - } - }), - ), - }) - }, + () => + Effect.gen(function* () { + const info = yield* createPty({ command: ps, title: "pwsh" }) + expect(info.args).toEqual([]) + }), { timeout: 30000 }, ) } @@ -44,62 +40,36 @@ describe("pty shell args", () => { return Shell.gitbash() })() if (bash) { - test( + it.instance( "adds login args to bash", - async () => { - await using dir = await tmpdir() - await WithInstance.provide({ - directory: dir.path, - fn: () => - AppRuntime.runPromise( - Effect.gen(function* () { - const pty = yield* Pty.Service - const info = yield* pty.create({ command: bash, title: "bash" }) - try { - expect(info.args).toEqual(["-l"]) - } finally { - yield* pty.remove(info.id) - } - }), - ), - }) - }, + () => + Effect.gen(function* () { + const info = yield* createPty({ command: bash, title: "bash" }) + expect(info.args).toEqual(["-l"]) + }), { timeout: 30000 }, ) } }) describe("pty configured shell", () => { - test( + const configured = process.platform === "win32" ? Bun.which("pwsh") || Bun.which("powershell") : Bun.which("bash") + + it.instance( "uses configured shell for default PTY command", - async () => { - const configured = process.platform === "win32" ? Bun.which("pwsh") || Bun.which("powershell") : Bun.which("bash") - if (!configured) return + () => + Effect.gen(function* () { + if (!configured) return - await using dir = await tmpdir({ - config: { shell: Shell.name(configured) }, - }) - await WithInstance.provide({ - directory: dir.path, - fn: () => - AppRuntime.runPromise( - Effect.gen(function* () { - const pty = yield* Pty.Service - const info = yield* pty.create({ title: "configured" }) - try { - if (process.platform === "win32") { - expect(info.command.toLowerCase()).toBe(configured.toLowerCase()) - } else { - expect(info.command).toBe(configured) - } - expect(info.args).toEqual(process.platform === "win32" ? [] : ["-l"]) - } finally { - yield* pty.remove(info.id) - } - }), - ), - }) - }, + const info = yield* createPty({ title: "configured" }) + if (process.platform === "win32") { + expect(info.command.toLowerCase()).toBe(configured.toLowerCase()) + } else { + expect(info.command).toBe(configured) + } + expect(info.args).toEqual(process.platform === "win32" ? [] : ["-l"]) + }), + configured ? { config: { shell: Shell.name(configured) } } : undefined, { timeout: 30000 }, ) }) From 46daede10c55288220dab68e61e90b7ca56f1eb7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 23:25:52 -0400 Subject: [PATCH 218/378] test(pty): migrate output isolation to Effect runner (#27235) --- .../test/pty/pty-output-isolation.test.ts | 301 +++++++++--------- 1 file changed, 158 insertions(+), 143 deletions(-) diff --git a/packages/opencode/test/pty/pty-output-isolation.test.ts b/packages/opencode/test/pty/pty-output-isolation.test.ts index 662042b64..0fa710f02 100644 --- a/packages/opencode/test/pty/pty-output-isolation.test.ts +++ b/packages/opencode/test/pty/pty-output-isolation.test.ts @@ -1,147 +1,162 @@ -import { describe, expect, test } from "bun:test" -import { AppRuntime } from "../../src/effect/app-runtime" -import { Effect } from "effect" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { describe, expect } from "bun:test" +import { Bus } from "../../src/bus" +import { Config } from "../../src/config/config" +import { Plugin } from "../../src/plugin" import { Pty } from "../../src/pty" -import { tmpdir } from "../fixture/fixture" -import { setTimeout as sleep } from "node:timers/promises" +import { Duration, Effect, Layer, Queue } from "effect" +import { testEffect } from "../lib/effect" + +type Socket = Parameters[1] + +const it = testEffect( + Pty.layer.pipe( + Layer.provideMerge(Bus.layer), + Layer.provideMerge(Config.defaultLayer), + Layer.provideMerge(Plugin.defaultLayer), + ), +) +const ptyTest = process.platform === "win32" ? it.instance.skip : it.instance + +const createPty = Effect.fn("PtyOutputIsolationTest.createPty")(function* (input: Pty.CreateInput) { + const pty = yield* Pty.Service + return yield* Effect.acquireRelease(pty.create(input), (info) => pty.remove(info.id).pipe(Effect.ignore)) +}) + +const decodeOutput = (data: string | Uint8Array | ArrayBuffer) => + typeof data === "string" + ? data + : Buffer.from(data instanceof Uint8Array ? data : new Uint8Array(data)).toString("utf8") + +const makeSocket = Effect.fn("PtyOutputIsolationTest.makeSocket")(function* (data: unknown) { + const output = yield* Queue.unbounded() + const chunks: string[] = [] + const socket: Socket = { + readyState: 1, + data, + send: (data) => { + const text = decodeOutput(data) + chunks.push(text) + Queue.offerUnsafe(output, text) + }, + close: () => { + // no-op (simulate abrupt drop) + }, + } + + return { socket, output, chunks } +}) + +const waitForOutput = (output: Queue.Queue, text: string, duration: Duration.Input = "5 seconds") => + Effect.gen(function* () { + let received = "" + while (!received.includes(text)) { + received += yield* Queue.take(output) + } + return received + }).pipe( + Effect.timeoutOrElse({ + duration, + orElse: () => Effect.fail(new Error(`timeout waiting for output containing ${JSON.stringify(text)}`)), + }), + ) + +const waitForLeakedOutput = (output: Queue.Queue, text: string) => + Effect.gen(function* () { + let received = "" + while (!received.includes(text)) { + received += yield* Queue.take(output) + } + return received + }).pipe( + Effect.timeoutOrElse({ + duration: "100 millis", + orElse: () => Effect.succeed(undefined), + }), + ) describe("pty", () => { - test("does not leak output when websocket objects are reused", async () => { - await using dir = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: dir.path, - fn: () => - AppRuntime.runPromise( - Effect.gen(function* () { - const pty = yield* Pty.Service - const a = yield* pty.create({ command: "cat", title: "a" }) - const b = yield* pty.create({ command: "cat", title: "b" }) - try { - const outA: string[] = [] - const outB: string[] = [] - - const ws = { - readyState: 1, - data: { events: { connection: "a" } }, - send: (data: unknown) => { - outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) - }, - close: () => { - // no-op (simulate abrupt drop) - }, - } - - yield* pty.connect(a.id, ws as any) - - ws.data = { events: { connection: "b" } } - ws.send = (data: unknown) => { - outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) - } - yield* pty.connect(b.id, ws as any) - - outA.length = 0 - outB.length = 0 - - yield* pty.write(a.id, "AAA\n") - yield* Effect.promise(() => sleep(100)) - - expect(outB.join("")).not.toContain("AAA") - } finally { - yield* pty.remove(a.id) - yield* pty.remove(b.id) - } - }), - ), - }) - }) - - test("does not leak output when Bun recycles websocket objects before re-connect", async () => { - await using dir = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: dir.path, - fn: () => - AppRuntime.runPromise( - Effect.gen(function* () { - const pty = yield* Pty.Service - const a = yield* pty.create({ command: "cat", title: "a" }) - try { - const outA: string[] = [] - const outB: string[] = [] - - const ws = { - readyState: 1, - data: { events: { connection: "a" } }, - send: (data: unknown) => { - outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) - }, - close: () => { - // no-op (simulate abrupt drop) - }, - } - - yield* pty.connect(a.id, ws as any) - outA.length = 0 - - ws.data = { events: { connection: "b" } } - ws.send = (data: unknown) => { - outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) - } - - yield* pty.write(a.id, "AAA\n") - yield* Effect.promise(() => sleep(100)) - - expect(outB.join("")).not.toContain("AAA") - } finally { - yield* pty.remove(a.id) - } - }), - ), - }) - }) - - test("treats in-place socket data mutation as the same connection", async () => { - await using dir = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: dir.path, - fn: () => - AppRuntime.runPromise( - Effect.gen(function* () { - const pty = yield* Pty.Service - const a = yield* pty.create({ command: "cat", title: "a" }) - try { - const out: string[] = [] - - const ctx = { connId: 1 } - const ws = { - readyState: 1, - data: ctx, - send: (data: unknown) => { - out.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) - }, - close: () => { - // no-op - }, - } - - yield* pty.connect(a.id, ws as any) - out.length = 0 - - ctx.connId = 2 - - yield* pty.write(a.id, "AAA\n") - yield* Effect.promise(() => sleep(100)) - - expect(out.join("")).toContain("AAA") - } finally { - yield* pty.remove(a.id) - } - }), - ), - }) - }) + ptyTest( + "does not leak output when websocket objects are reused", + () => + Effect.gen(function* () { + const pty = yield* Pty.Service + const a = yield* createPty({ command: "cat", title: "a" }) + const b = yield* createPty({ command: "cat", title: "b" }) + const connectionA = yield* makeSocket({ events: { connection: "a" } }) + const connectionB = { events: { connection: "b" } } + + yield* pty.connect(a.id, connectionA.socket) + + const outBQueue = yield* Queue.unbounded() + const outB: string[] = [] + connectionA.socket.data = connectionB + connectionA.socket.send = (data) => { + const text = decodeOutput(data) + outB.push(text) + Queue.offerUnsafe(outBQueue, text) + } + yield* pty.connect(b.id, connectionA.socket) + + connectionA.chunks.length = 0 + outB.length = 0 + + yield* pty.write(a.id, "AAA\n") + const verifyA = yield* makeSocket({ events: { connection: "verify-a" } }) + yield* pty.connect(a.id, verifyA.socket) + yield* waitForOutput(verifyA.output, "AAA") + + expect(outB.join("")).not.toContain("AAA") + expect(yield* waitForLeakedOutput(outBQueue, "AAA")).toBeUndefined() + }), + { git: true }, + ) + + ptyTest( + "does not leak output when Bun recycles websocket objects before re-connect", + () => + Effect.gen(function* () { + const pty = yield* Pty.Service + const a = yield* createPty({ command: "cat", title: "a" }) + const outA = yield* makeSocket({ events: { connection: "a" } }) + const outB = yield* Queue.unbounded() + + yield* pty.connect(a.id, outA.socket) + outA.chunks.length = 0 + + const connectionB = { events: { connection: "b" } } + outA.socket.data = connectionB + outA.socket.send = (data) => { + Queue.offerUnsafe(outB, decodeOutput(data)) + } + + yield* pty.write(a.id, "AAA\n") + const verifyA = yield* makeSocket({ events: { connection: "verify-a" } }) + yield* pty.connect(a.id, verifyA.socket) + yield* waitForOutput(verifyA.output, "AAA") + + expect(yield* waitForLeakedOutput(outB, "AAA")).toBeUndefined() + }), + { git: true }, + ) + + ptyTest( + "treats in-place socket data mutation as the same connection", + () => + Effect.gen(function* () { + const pty = yield* Pty.Service + const a = yield* createPty({ command: "cat", title: "a" }) + const ctx = { connId: 1 } + const out = yield* makeSocket(ctx) + + yield* pty.connect(a.id, out.socket) + out.chunks.length = 0 + + ctx.connId = 2 + + yield* pty.write(a.id, "AAA\n") + + expect(yield* waitForOutput(out.output, "AAA")).toContain("AAA") + }), + { git: true }, + ) }) From ad6a8a185045d691b3f293b6a02629bcb9e61740 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 13 May 2026 11:28:08 +0800 Subject: [PATCH 219/378] fix: use htmlrewriter2 instead of HTMLRewriter for node compat (#26309) --- bun.lock | 1 + packages/opencode/package.json | 1 + packages/opencode/src/tool/webfetch.ts | 50 ++++++++------------ packages/opencode/test/tool/webfetch.test.ts | 19 ++++++++ 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/bun.lock b/bun.lock index 76602e885..e8ff7adaf 100644 --- a/bun.lock +++ b/bun.lock @@ -430,6 +430,7 @@ "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", + "htmlparser2": "8.0.2", "ignore": "7.0.5", "immer": "11.1.4", "jsonc-parser": "3.3.1", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index b0427f231..48cb7b345 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -141,6 +141,7 @@ "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", + "htmlparser2": "8.0.2", "ignore": "7.0.5", "immer": "11.1.4", "jsonc-parser": "3.3.1", diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index 8c2be44e9..f8a4b6233 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -1,5 +1,6 @@ import { Effect, Schema } from "effect" import { HttpClient, HttpClientRequest } from "effect/unstable/http" +import { Parser } from "htmlparser2" import * as Tool from "./tool" import TurndownService from "turndown" import DESCRIPTION from "./webfetch.txt" @@ -139,8 +140,7 @@ export const WebFetchTool = Tool.define( case "text": if (contentType.includes("text/html")) { - const text = yield* Effect.promise(() => extractTextFromHTML(content)) - return { output: text, title, metadata: {} } + return { output: extractTextFromHTML(content), title, metadata: {} } } return { output: content, title, metadata: {} } @@ -155,35 +155,27 @@ export const WebFetchTool = Tool.define( }), ) -async function extractTextFromHTML(html: string) { +function extractTextFromHTML(html: string) { let text = "" - let skipContent = false - - const rewriter = new HTMLRewriter() - .on("script, style, noscript, iframe, object, embed", { - element() { - skipContent = true - }, - text() { - // Skip text content inside these elements - }, - }) - .on("*", { - element(element) { - // Reset skip flag when entering other elements - if (!["script", "style", "noscript", "iframe", "object", "embed"].includes(element.tagName)) { - skipContent = false - } - }, - text(input) { - if (!skipContent) { - text += input.text - } - }, - }) - .transform(new Response(html)) + let skipDepth = 0 + + const parser = new Parser({ + onopentag(name) { + if (skipDepth > 0 || ["script", "style", "noscript", "iframe", "object", "embed"].includes(name)) { + skipDepth++ + } + }, + ontext(input) { + if (skipDepth === 0) text += input + }, + onclosetag() { + if (skipDepth > 0) skipDepth-- + }, + }) + + parser.write(html) + parser.end() - await rewriter.text() return text.trim() } diff --git a/packages/opencode/test/tool/webfetch.test.ts b/packages/opencode/test/tool/webfetch.test.ts index 804c6bde2..fdf5210b9 100644 --- a/packages/opencode/test/tool/webfetch.test.ts +++ b/packages/opencode/test/tool/webfetch.test.ts @@ -91,4 +91,23 @@ describe("tool.webfetch", () => { }), ), ) + + it.instance("extracts text from html without scripts or styles", () => + withFetch( + () => + new Response( + "Hello world", + { + status: 200, + headers: { "content-type": "text/html; charset=utf-8" }, + }, + ), + (url) => + Effect.gen(function* () { + const result = yield* exec({ url: new URL("/page.html", url).toString(), format: "text" }) + expect(result.output).toBe("Hello world") + expect(result.attachments).toBeUndefined() + }), + ), + ) }) From c5961205ef2e0d10b77cf829271711e5e7cc83c6 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 03:44:43 +0000 Subject: [PATCH 220/378] 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 70372b820..0bba38a2c 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-CF+ecq3PqUuA7ta09oYkDaFz+FxZqQragu9xvtw++RE=", - "aarch64-linux": "sha256-QVdgdCncl+XEIx569oNNRXyxRh5mkEMJSTSO1I3QfzM=", - "aarch64-darwin": "sha256-dfP1SEpgQ63DEfG4eRD7BWGoMPFmjGSWiW4M0J4Asfg=", - "x86_64-darwin": "sha256-JgXg+BwZTWroChnxE6akoEFfdb7vVH0qBMZSZ7TJhDU=" + "x86_64-linux": "sha256-xZyIgqow1wVh0Kfpb5GLUUHsE3jyfqJfrZ9Qykml008=", + "aarch64-linux": "sha256-tbbne63KImq4EQrPi45l9YG1dY/SO7b1ZKkLjDfZhWg=", + "aarch64-darwin": "sha256-PYsiSMkASbcZxqMXb7UfbkRTiQae6xzseMNhDP+/y5g=", + "x86_64-darwin": "sha256-Qnj9FAgXWyiB6U5NyIsRw7aNVNexAagETr07Jwde908=" } } From 3be65dff4622f4bb6a0a64d26d16cdbe33fb1ee3 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 13 May 2026 12:09:36 +0800 Subject: [PATCH 221/378] fix: add optional chaining to session_status access (#27247) --- packages/app/src/context/global-sync/child-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index af90fab6b..0c4c68b7f 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -209,7 +209,7 @@ export function createChildStoreManager(input: { sessionTotal: 0, session_status: {}, session_working(id: string) { - return this.session_status[id].type !== "idle" + return this.session_status[id]?.type !== "idle" }, session_diff: {}, todo: {}, From 67e6408ceff89ec3ed3f56c8e96df539607e8504 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 12 May 2026 23:21:31 -0500 Subject: [PATCH 222/378] fix: disable image module for now (#27248) --- packages/opencode/src/session/processor.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 579c4cc42..506ec0c40 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -402,10 +402,14 @@ export const layer: Layer.Layer< typeof attachment.mime === "string" && typeof attachment.url === "string", ) + // temporarily disabled + // const normalized = yield* Effect.forEach(toolAttachments, (attachment) => + // attachment.mime.startsWith("image/") + // ? image.normalize(attachment).pipe(Effect.exit) + // : Effect.succeed(Exit.succeed(attachment)), + // ) const normalized = yield* Effect.forEach(toolAttachments, (attachment) => - attachment.mime.startsWith("image/") - ? image.normalize(attachment).pipe(Effect.exit) - : Effect.succeed(Exit.succeed(attachment)), + Effect.succeed(Exit.succeed(attachment)), ) const omitted = normalized.filter(Exit.isFailure).length const attachments = normalized.filter(Exit.isSuccess).map((item) => item.value) @@ -414,7 +418,7 @@ export const layer: Layer.Layer< output: omitted === 0 ? value.output.output - : `${value.output.output}\n\n[${omitted} image${omitted === 1 ? "" : "s"} omitted: could not be resized below the inline image size limit.]`, + : `${value.output.output}\n\n[${omitted} image${omitted === 1 ? "" : "s"} omitted: could not be resized below the image size limit.]`, attachments: attachments?.length ? attachments : undefined, } // TODO(v2): Temporary dual-write while migrating session messages to v2 events. From 90c13d93f3a2fb2df80e798668dccb3b973e5423 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 10:15:28 +0530 Subject: [PATCH 223/378] fix(server): hide unknown 500 details (#27251) --- .../instance/httpapi/middleware/error.ts | 2 +- .../server/httpapi-error-middleware.test.ts | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/test/server/httpapi-error-middleware.test.ts diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index 6f3c33a64..585f4bd18 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -48,7 +48,7 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) return Effect.succeed( HttpServerResponse.jsonUnsafe( new NamedError.Unknown({ - message: error instanceof Error && error.stack ? error.stack : String(error), + message: "Unexpected server error. Check server logs for details.", }).toObject(), { status: 500 }, ), diff --git a/packages/opencode/test/server/httpapi-error-middleware.test.ts b/packages/opencode/test/server/httpapi-error-middleware.test.ts new file mode 100644 index 000000000..a07e31382 --- /dev/null +++ b/packages/opencode/test/server/httpapi-error-middleware.test.ts @@ -0,0 +1,30 @@ +import { NodeHttpServer, NodeServices } from "@effect/platform-node" +import { describe, expect } from "bun:test" +import { Effect, Layer } from "effect" +import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http" +import { errorLayer } from "../../src/server/routes/instance/httpapi/middleware/error" +import { testEffect } from "../lib/effect" + +const it = testEffect(Layer.mergeAll(NodeHttpServer.layerTest, NodeServices.layer)) + +describe("HttpApi error middleware", () => { + it.live("returns a safe body for unknown 500 defects", () => + Effect.gen(function* () { + yield* HttpRouter.add("GET", "/boom", Effect.die(new Error("secret stack marker"))).pipe( + Layer.provide(errorLayer), + HttpRouter.serve, + Layer.build, + ) + + const response = yield* HttpClientRequest.get("/boom").pipe(HttpClient.execute) + const body = yield* response.json + + expect(response.status).toBe(500) + expect(body).toEqual({ + name: "UnknownError", + data: { message: "Unexpected server error. Check server logs for details." }, + }) + expect(JSON.stringify(body)).not.toContain("secret stack marker") + }), + ) +}) From 10b99b2f5a3b5c8bf69a1542d21b1ee14145d1d3 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 13 May 2026 12:52:18 +0800 Subject: [PATCH 224/378] build(ci): use native arm64 runner for desktop linux arm64 builds (#27250) --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bef1e7029..5f8bac973 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -244,9 +244,9 @@ jobs: - host: "blacksmith-4vcpu-ubuntu-2404" target: x86_64-unknown-linux-gnu platform_flag: --linux - - host: "blacksmith-4vcpu-ubuntu-2404" + - host: "blacksmith-4vcpu-ubuntu-2404-arm" target: aarch64-unknown-linux-gnu - platform_flag: --linux + platform_flag: --linux --arm64 runs-on: ${{ matrix.settings.host }} steps: - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 From 28204720dcb6f3d42d1015432fff0acecef10b3a Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 12 May 2026 23:55:45 -0500 Subject: [PATCH 225/378] temporarily revert: preserve permission ordering by accepting a layered array (#27258) --- packages/opencode/src/agent/agent.ts | 6 +- packages/opencode/src/config/config.ts | 25 +-- packages/opencode/src/config/permission.ts | 8 - packages/opencode/test/config/config.test.ts | 178 +----------------- .../opencode/test/permission-task.test.ts | 25 +-- 5 files changed, 18 insertions(+), 224 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 74ca1a402..423a51318 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -1,5 +1,4 @@ import { Config } from "@/config/config" -import { ConfigPermission } from "@/config/permission" import { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "../provider/schema" import { generateObject, streamObject, type ModelMessage } from "ai" @@ -118,10 +117,7 @@ export const layer = Layer.effect( }, }) - // Convert permission layers to rulesets and merge them - // Each layer's rules come after the previous, so later configs override earlier ones - const layers = ConfigPermission.toLayers(cfg.permission) - const user = Permission.merge(...layers.map((p) => Permission.fromConfig(p))) + const user = Permission.fromConfig(cfg.permission ?? {}) const agents: Record = { build: { diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 78bb83ed8..545e48e64 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -55,16 +55,6 @@ function mergeConfigConcatArrays(target: Info, source: Info): Info { if (target.instructions && source.instructions) { merged.instructions = Array.from(new Set([...target.instructions, ...source.instructions])) } - // Accumulate permission layers for later merging as rulesets. - // This preserves the ordering semantics: later rules override earlier rules. - // Each layer keeps the raw shape the user wrote on disk; consumers should use - // ConfigPermission.toLayers to normalise. - if (source.permission) { - merged.permission = [ - ...ConfigPermission.toLayers(target.permission), - ...ConfigPermission.toLayers(source.permission), - ] - } return merged } @@ -238,12 +228,7 @@ export const Info = Schema.Struct({ description: "Additional instruction files or patterns to include", }), layout: Schema.optional(ConfigLayout.Layout).annotate({ description: "@deprecated Always uses stretch layout." }), - permission: Schema.optional( - Schema.Union([ConfigPermission.Info, Schema.mutable(Schema.Array(ConfigPermission.Info))]), - ).annotate({ - description: - "Permission configuration. Accepts a single object (per-tool action map) or an array of layered configs; arrays are merged in order so later layers override earlier ones.", - }), + permission: Schema.optional(ConfigPermission.Info), tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), attachment: Schema.optional(ConfigAttachment.Info).annotate({ description: "Attachment processing configuration, including image size limits and resizing behavior", @@ -719,12 +704,11 @@ export const layer = Layer.effect( } if (Flag.OPENCODE_PERMISSION) { - const envPermission = JSON.parse(Flag.OPENCODE_PERMISSION) as ConfigPermission.Info - result.permission = [...ConfigPermission.toLayers(result.permission), envPermission] + result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION)) } if (result.tools) { - const perms: ConfigPermission.Info = {} + const perms: Record = {} for (const [tool, enabled] of Object.entries(result.tools)) { const action: ConfigPermission.Action = enabled ? "allow" : "deny" if (tool === "write" || tool === "edit" || tool === "patch") { @@ -733,8 +717,7 @@ export const layer = Layer.effect( } perms[tool] = action } - // Tools permissions come before other permissions (they can be overridden) - result.permission = [perms, ...ConfigPermission.toLayers(result.permission)] + result.permission = mergeDeep(perms, result.permission ?? {}) } if (!result.username) result.username = os.userInfo().username diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts index c780d2443..1092ae2b7 100644 --- a/packages/opencode/src/config/permission.ts +++ b/packages/opencode/src/config/permission.ts @@ -56,11 +56,3 @@ export const Info = InputSchema.pipe( ).annotate({ identifier: "PermissionConfig" }) type _Info = Schema.Schema.Type export type Info = { -readonly [K in keyof _Info]: _Info[K] } - -// Top-level config accepts either a single permission object or an array of -// layered configs. Internal merging produces arrays; this helper normalises -// either shape into the array form expected by consumers. -export function toLayers(value: Info | Info[] | undefined): Info[] { - if (!value) return [] - return Array.isArray(value) ? value : [value] -} diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index a2e439177..90e78efcd 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -3,9 +3,7 @@ import { Effect, Layer, Option } from "effect" import { NodeFileSystem, NodePath } from "@effect/platform-node" import { Config } from "@/config/config" import { ConfigManaged } from "@/config/managed" -import { ConfigPermission } from "@/config/permission" import { ConfigParse } from "../../src/config/parse" -import { Permission } from "../../src/permission" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { Instance } from "../../src/project/instance" @@ -278,40 +276,6 @@ test("updates global config and omits empty shell key in json", async () => { } }) -test("global config update preserves single-object permission shape on disk", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - shell: "bash", - permission: { bash: "ask" }, - }), - ) - }, - }) - - const prev = Global.Path.config - ;(Global.Path as { config: string }).config = tmp.path - await clear(true) - - try { - // Updating an unrelated key must not rewrite `permission` from object to array form. - await saveGlobal({ shell: "zsh" }) - - const written = await Filesystem.readJson<{ permission?: unknown; shell?: string }>( - path.join(tmp.path, "opencode.json"), - ) - expect(written.shell).toBe("zsh") - expect(Array.isArray(written.permission)).toBe(false) - expect(written.permission).toEqual({ bash: "ask" }) - } finally { - ;(Global.Path as { config: string }).config = prev - await clear(true) - } -}) - test("updates global config and omits empty shell key in jsonc", async () => { await using tmp = await tmpdir({ init: async (dir) => { @@ -1749,10 +1713,7 @@ test("permission config preserves user key order", async () => { directory: tmp.path, fn: async () => { const config = await load() - // load() goes through the merge pipeline, producing the layered array form - expect(config.permission).toHaveLength(1) - const perm = (config.permission as ConfigPermission.Info[])[0] - expect(Object.keys(perm)).toEqual([ + expect(Object.keys(config.permission!)).toEqual([ "*", "edit", "write", @@ -1768,129 +1729,6 @@ test("permission config preserves user key order", async () => { }) }) -// Global bash "rm *" deny is inherited, but user's top-level "*" ask comes after and overrides it -test("user top-level catchall overrides inherited bash rules", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - permission: { - bash: { "rm *": "deny" }, - }, - }), - ) - const opencodeDir = path.join(dir, ".opencode") - await fs.mkdir(opencodeDir, { recursive: true }) - await Filesystem.write( - path.join(opencodeDir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - permission: { - "*": "ask", - bash: { "ls *": "allow" }, - }, - }), - ) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() - const layers = ConfigPermission.toLayers(config.permission) - const ruleset = Permission.merge(...layers.map((p) => Permission.fromConfig(p))) - - expect(Permission.evaluate("bash", "rm -rf /", ruleset).action).toBe("ask") - expect(Permission.evaluate("bash", "ls -la", ruleset).action).toBe("allow") - expect(Permission.evaluate("bash", "echo hello", ruleset).action).toBe("ask") - }, - }) -}) - -// No top-level catchall, so global bash "rm *" deny is preserved -test("inherited bash rules apply when no user top-level catchall", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - permission: { - bash: { "rm *": "deny" }, - }, - }), - ) - const opencodeDir = path.join(dir, ".opencode") - await fs.mkdir(opencodeDir, { recursive: true }) - await Filesystem.write( - path.join(opencodeDir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - permission: { - bash: { "ls *": "allow" }, - }, - }), - ) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() - const layers = ConfigPermission.toLayers(config.permission) - const ruleset = Permission.merge(...layers.map((p) => Permission.fromConfig(p))) - - expect(Permission.evaluate("bash", "rm -rf /", ruleset).action).toBe("deny") - expect(Permission.evaluate("bash", "ls -la", ruleset).action).toBe("allow") - }, - }) -}) - -// User's bash "*" catchall overrides global "rm *" deny -test("user bash catchall overrides inherited bash rules", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - permission: { - bash: { "rm *": "deny" }, - }, - }), - ) - const opencodeDir = path.join(dir, ".opencode") - await fs.mkdir(opencodeDir, { recursive: true }) - await Filesystem.write( - path.join(opencodeDir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - permission: { - bash: { "*": "ask", "ls *": "allow" }, - }, - }), - ) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() - const layers = ConfigPermission.toLayers(config.permission) - const ruleset = Permission.merge(...layers.map((p) => Permission.fromConfig(p))) - - expect(Permission.evaluate("bash", "rm -rf /", ruleset).action).toBe("ask") - expect(Permission.evaluate("bash", "ls -la", ruleset).action).toBe("allow") - expect(Permission.evaluate("bash", "echo hello", ruleset).action).toBe("ask") - - // Non-bash permissions should use the top-level "*" rule - expect(Permission.evaluate("read", "foo.txt", ruleset).action).toBe("ask") - }, - }) -}) - test("config parser preserves permission order while rejecting unknown top-level keys", () => { const config = ConfigParse.schema( Config.Info, @@ -1904,8 +1742,7 @@ test("config parser preserves permission order while rejecting unknown top-level "test", ) - // ConfigParse.schema preserves the raw shape the user wrote - expect(Object.keys(config.permission as ConfigPermission.Info)).toEqual(["bash", "*", "edit"]) + expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"]) try { ConfigParse.schema(Config.Info, { invalid_field: true }, "test") throw new Error("expected config parse to fail") @@ -2742,12 +2579,11 @@ test("parseManagedPlist parses permission rules", async () => { ), "test:mobileconfig", ) - const perm = config.permission as ConfigPermission.Info - expect(perm?.["*"]).toBe("ask") - expect(perm?.grep).toBe("allow") - expect(perm?.webfetch).toBe("ask") - expect(perm?.["~/.ssh/*"]).toBe("deny") - const bash = perm?.bash as Record + expect(config.permission?.["*"]).toBe("ask") + expect(config.permission?.grep).toBe("allow") + expect(config.permission?.webfetch).toBe("ask") + expect(config.permission?.["~/.ssh/*"]).toBe("deny") + const bash = config.permission?.bash as Record expect(bash?.["rm -rf *"]).toBe("deny") expect(bash?.["curl *"]).toBe("deny") }) diff --git a/packages/opencode/test/permission-task.test.ts b/packages/opencode/test/permission-task.test.ts index 77ceda8a4..64b93bb8b 100644 --- a/packages/opencode/test/permission-task.test.ts +++ b/packages/opencode/test/permission-task.test.ts @@ -1,7 +1,6 @@ import { afterEach, describe, test, expect } from "bun:test" import { Permission } from "../src/permission" import { Config } from "@/config/config" -import { ConfigPermission } from "@/config/permission" import { Instance } from "../src/project/instance" import { WithInstance } from "../src/project/with-instance" import { disposeAllInstances, tmpdir } from "./fixture/fixture" @@ -164,9 +163,7 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.merge( - ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), - ) + const ruleset = Permission.fromConfig(config.permission ?? {}) // general and orchestrator-fast should be allowed, code-reviewer denied expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow") @@ -191,9 +188,7 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.merge( - ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), - ) + const ruleset = Permission.fromConfig(config.permission ?? {}) // general and code-reviewer should be ask, orchestrator-* denied expect(Permission.evaluate("task", "general", ruleset).action).toBe("ask") expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask") @@ -218,9 +213,7 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.merge( - ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), - ) + const ruleset = Permission.fromConfig(config.permission ?? {}) expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny") // Unspecified agents default to "ask" @@ -247,9 +240,7 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.merge( - ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), - ) + const ruleset = Permission.fromConfig(config.permission ?? {}) // Verify task permissions expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") @@ -287,9 +278,7 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.merge( - ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), - ) + const ruleset = Permission.fromConfig(config.permission ?? {}) // Last matching rule wins - "*" deny is last, so all agents are denied expect(Permission.evaluate("task", "general", ruleset).action).toBe("deny") @@ -320,9 +309,7 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.merge( - ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), - ) + const ruleset = Permission.fromConfig(config.permission ?? {}) // Evaluate uses findLast - "general" allow comes after "*" deny expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") From bed88ce50ebcb11a3f4fc801280b380b6aa65208 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 04:56:52 +0000 Subject: [PATCH 226/378] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 5 +---- packages/sdk/openapi.json | 13 +------------ 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 9789d3099..72b2a9f16 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1263,10 +1263,7 @@ export type Config = { } instructions?: Array layout?: LayoutConfig - /** - * Permission configuration. Accepts a single object (per-tool action map) or an array of layered configs; arrays are merged in order so later layers override earlier ones. - */ - permission?: PermissionConfig | Array + permission?: PermissionConfig tools?: { [key: string]: boolean } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index b6f421dd0..fbbe237ec 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -12407,18 +12407,7 @@ "$ref": "#/components/schemas/LayoutConfig" }, "permission": { - "anyOf": [ - { - "$ref": "#/components/schemas/PermissionConfig" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/PermissionConfig" - } - } - ], - "description": "Permission configuration. Accepts a single object (per-tool action map) or an array of layered configs; arrays are merged in order so later layers override earlier ones." + "$ref": "#/components/schemas/PermissionConfig" }, "tools": { "type": "object", From 4aaece29d9a37a225597021d9df2edb72f017410 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 13 May 2026 13:14:39 +0800 Subject: [PATCH 227/378] feat(desktop): reintroduce AppStream MetaInfo for Linux desktop builds (#27253) --- packages/desktop/.gitignore | 1 + packages/desktop/scripts/copy-metainfo.ts | 47 +++++++++++++++++++++++ packages/desktop/scripts/prebuild.ts | 1 + 3 files changed, 49 insertions(+) create mode 100644 packages/desktop/scripts/copy-metainfo.ts diff --git a/packages/desktop/.gitignore b/packages/desktop/.gitignore index ac9d8db96..6923045cd 100644 --- a/packages/desktop/.gitignore +++ b/packages/desktop/.gitignore @@ -26,3 +26,4 @@ out/ resources/opencode-cli* resources/icons +resources/*.metainfo.xml diff --git a/packages/desktop/scripts/copy-metainfo.ts b/packages/desktop/scripts/copy-metainfo.ts new file mode 100644 index 000000000..e7585ccaf --- /dev/null +++ b/packages/desktop/scripts/copy-metainfo.ts @@ -0,0 +1,47 @@ +import { resolveChannel } from "./utils" + +const arg = process.argv[2] +const channel = arg === "dev" || arg === "beta" || arg === "prod" ? arg : resolveChannel() + +const appId = channel === "prod" ? "ai.opencode.desktop" : `ai.opencode.desktop.${channel}` +const productName = channel === "prod" ? "OpenCode" : `OpenCode ${channel.charAt(0).toUpperCase() + channel.slice(1)}` +const summary = `Open source AI coding agent${channel !== "prod" ? ` (${channel})` : ""}` + +const xml = ` + + ${appId} + + CC0-1.0 + MIT + + ${productName} + ${summary} + + + Anomaly Innovations Inc. + + + +

+ OpenCode is an open source agent that helps you write and run code with any AI model. +

+
+ + ${appId}.desktop + + + + https://github.com/anomalyco/opencode/issues + https://opencode.ai + https://github.com/anomalyco/opencode + + + + https://raw.githubusercontent.com/anomalyco/opencode/b75d4d1c5ec449585d515c756fc81f080a157a9a/packages/web/src/assets/lander/screenshot.png + + +
+` + +await Bun.write(`resources/${appId}.metainfo.xml`, xml) +console.log(`Generated metainfo for ${channel} at resources/${appId}.metainfo.xml`) diff --git a/packages/desktop/scripts/prebuild.ts b/packages/desktop/scripts/prebuild.ts index 46a2475ea..79b0e30af 100644 --- a/packages/desktop/scripts/prebuild.ts +++ b/packages/desktop/scripts/prebuild.ts @@ -5,5 +5,6 @@ import { resolveChannel } from "./utils" const channel = resolveChannel() await $`bun ./scripts/copy-icons.ts ${channel}` +await $`bun ./scripts/copy-metainfo.ts ${channel}` await $`cd ../opencode && bun script/build-node.ts` From 367665dba23f967202a3b4ab6f01c63ca5e83440 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 11:01:18 +0530 Subject: [PATCH 228/378] fix(cli): render tagged config errors (#27256) --- packages/opencode/src/cli/error.ts | 62 +++++++++++++++++------- packages/opencode/test/cli/error.test.ts | 35 +++++++++++++ 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index 6fd7b573e..9551fac1e 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -5,13 +5,37 @@ interface ErrorLike { name?: string _tag?: string message?: string - data?: Record + data?: Record +} + +type ConfigIssue = { message: string; path: string[] } + +function isRecord(input: unknown): input is Record { + return typeof input === "object" && input !== null } function isTaggedError(error: unknown, tag: string): boolean { - return ( - typeof error === "object" && error !== null && "_tag" in error && (error as Record)._tag === tag - ) + return isRecord(error) && error._tag === tag +} + +function configData(input: unknown, tag: string): Record | undefined { + if (!isRecord(input)) return undefined + if (input.name === tag && isRecord(input.data)) return input.data + if (input._tag === tag) return input + return undefined +} + +function stringField(input: Record, key: string): string | undefined { + return typeof input[key] === "string" ? input[key] : undefined +} + +function configIssues(input: Record): ConfigIssue[] { + return Array.isArray(input.issues) + ? input.issues.filter((issue): issue is ConfigIssue => { + if (!isRecord(issue)) return false + return typeof issue.message === "string" && Array.isArray(issue.path) && issue.path.every((x) => typeof x === "string") + }) + : [] } export function FormatError(input: unknown) { @@ -35,7 +59,7 @@ export function FormatError(input: unknown) { // ProviderModelNotFoundError: { providerID: string, modelID: string, suggestions?: string[] } if (NamedError.hasName(input, "ProviderModelNotFoundError")) { const data = (input as ErrorLike).data - const suggestions: string[] = Array.isArray(data?.suggestions) ? data.suggestions : [] + const suggestions = Array.isArray(data?.suggestions) ? data.suggestions.filter((x) => typeof x === "string") : [] return [ `Model not found: ${data?.providerID}/${data?.modelID}`, ...(suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []), @@ -50,28 +74,30 @@ export function FormatError(input: unknown) { } // ConfigJsonError: { path: string, message?: string } - if (NamedError.hasName(input, "ConfigJsonError")) { - const data = (input as ErrorLike).data - return `Config file at ${data?.path} is not valid JSON(C)` + (data?.message ? `: ${data.message}` : "") + const configJson = configData(input, "ConfigJsonError") + if (configJson) { + const message = stringField(configJson, "message") + return `Config file at ${stringField(configJson, "path")} is not valid JSON(C)` + (message ? `: ${message}` : "") } // ConfigDirectoryTypoError: { dir: string, path: string, suggestion: string } - if (NamedError.hasName(input, "ConfigDirectoryTypoError")) { - const data = (input as ErrorLike).data - return `Directory "${data?.dir}" in ${data?.path} is not valid. Rename the directory to "${data?.suggestion}" or remove it. This is a common typo.` + const configDirectoryTypo = configData(input, "ConfigDirectoryTypoError") + if (configDirectoryTypo) { + return `Directory "${stringField(configDirectoryTypo, "dir")}" in ${stringField(configDirectoryTypo, "path")} is not valid. Rename the directory to "${stringField(configDirectoryTypo, "suggestion")}" or remove it. This is a common typo.` } // ConfigFrontmatterError: { message: string } - if (NamedError.hasName(input, "ConfigFrontmatterError")) { - return (input as ErrorLike).data?.message ?? "" + const configFrontmatter = configData(input, "ConfigFrontmatterError") + if (configFrontmatter) { + return stringField(configFrontmatter, "message") ?? "" } // ConfigInvalidError: { path?: string, message?: string, issues?: Array<{ message: string, path: string[] }> } - if (NamedError.hasName(input, "ConfigInvalidError")) { - const data = (input as ErrorLike).data - const path = data?.path - const message = data?.message - const issues: Array<{ message: string; path: string[] }> = Array.isArray(data?.issues) ? data.issues : [] + const configInvalid = configData(input, "ConfigInvalidError") + if (configInvalid) { + const path = stringField(configInvalid, "path") + const message = stringField(configInvalid, "message") + const issues = configIssues(configInvalid) return [ `Configuration is invalid${path && path !== "config" ? ` at ${path}` : ""}` + (message ? `: ${message}` : ""), ...issues.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")), diff --git a/packages/opencode/test/cli/error.test.ts b/packages/opencode/test/cli/error.test.ts index b4d1dbeda..e9ede9cdb 100644 --- a/packages/opencode/test/cli/error.test.ts +++ b/packages/opencode/test/cli/error.test.ts @@ -4,6 +4,41 @@ import { FormatError } from "../../src/cli/error" import { UI } from "../../src/cli/ui" describe("cli.error", () => { + test("formats legacy and tagged config errors the same way", () => { + const cases = [ + { + tag: "ConfigJsonError", + data: { path: "/tmp/opencode.jsonc", message: "Unexpected token" }, + expected: "Config file at /tmp/opencode.jsonc is not valid JSON(C): Unexpected token", + }, + { + tag: "ConfigDirectoryTypoError", + data: { path: "/tmp/opencode.jsonc", dir: ".opencode", suggestion: "opencode" }, + expected: + 'Directory ".opencode" in /tmp/opencode.jsonc is not valid. Rename the directory to "opencode" or remove it. This is a common typo.', + }, + { + tag: "ConfigFrontmatterError", + data: { path: "/tmp/AGENTS.md", message: "failed frontmatter" }, + expected: "failed frontmatter", + }, + { + tag: "ConfigInvalidError", + data: { + path: "/tmp/opencode.jsonc", + message: "schema mismatch", + issues: [{ message: "Expected string", path: ["provider", "id"] }], + }, + expected: "Configuration is invalid at /tmp/opencode.jsonc: schema mismatch\n↳ Expected string provider.id", + }, + ] + + for (const item of cases) { + expect(FormatError({ name: item.tag, data: item.data })).toBe(item.expected) + expect(FormatError({ _tag: item.tag, ...item.data })).toBe(item.expected) + } + }) + test("formats account transport errors clearly", () => { const error = new AccountTransportError({ method: "POST", From d3d7b44e73e4b63f2fe382cc95613759ba5f4525 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 05:32:32 +0000 Subject: [PATCH 229/378] chore: generate --- packages/opencode/src/cli/error.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index 9551fac1e..2bb0cb51f 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -33,7 +33,11 @@ function configIssues(input: Record): ConfigIssue[] { return Array.isArray(input.issues) ? input.issues.filter((issue): issue is ConfigIssue => { if (!isRecord(issue)) return false - return typeof issue.message === "string" && Array.isArray(issue.path) && issue.path.every((x) => typeof x === "string") + return ( + typeof issue.message === "string" && + Array.isArray(issue.path) && + issue.path.every((x) => typeof x === "string") + ) }) : [] } From dd46fddf22c17d5177a3a4efa17ca720d4b6327d Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 11:10:12 +0530 Subject: [PATCH 230/378] test(cli): cover config json diagnostics (#27257) --- packages/opencode/test/cli/error.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/opencode/test/cli/error.test.ts b/packages/opencode/test/cli/error.test.ts index e9ede9cdb..09119a4ff 100644 --- a/packages/opencode/test/cli/error.test.ts +++ b/packages/opencode/test/cli/error.test.ts @@ -39,6 +39,17 @@ describe("cli.error", () => { } }) + test("preserves multiline JSONC diagnostics for tagged config errors", () => { + const data = { + path: "/tmp/opencode.jsonc", + message: '\n--- JSONC Input ---\n{\n "model": \n}\n--- Errors ---\nValueExpected at line 3, column 1\n Line 3: }\n ^\n--- End ---', + } + const expected = `Config file at ${data.path} is not valid JSON(C): ${data.message}` + + expect(FormatError({ name: "ConfigJsonError", data })).toBe(expected) + expect(FormatError({ _tag: "ConfigJsonError", ...data })).toBe(expected) + }) + test("formats account transport errors clearly", () => { const error = new AccountTransportError({ method: "POST", From 9fe912439b23b44e0176239f6dc91231c7d7a249 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 05:41:29 +0000 Subject: [PATCH 231/378] chore: generate --- packages/opencode/test/cli/error.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/cli/error.test.ts b/packages/opencode/test/cli/error.test.ts index 09119a4ff..6c5ab265b 100644 --- a/packages/opencode/test/cli/error.test.ts +++ b/packages/opencode/test/cli/error.test.ts @@ -42,7 +42,8 @@ describe("cli.error", () => { test("preserves multiline JSONC diagnostics for tagged config errors", () => { const data = { path: "/tmp/opencode.jsonc", - message: '\n--- JSONC Input ---\n{\n "model": \n}\n--- Errors ---\nValueExpected at line 3, column 1\n Line 3: }\n ^\n--- End ---', + message: + '\n--- JSONC Input ---\n{\n "model": \n}\n--- Errors ---\nValueExpected at line 3, column 1\n Line 3: }\n ^\n--- End ---', } const expected = `Config file at ${data.path} is not valid JSON(C): ${data.message}` From d93a06431e8746fb0b5df2ad2431a6e56b79d050 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 13 May 2026 14:36:00 +0800 Subject: [PATCH 232/378] refactor(app): clarify session_working logic in child-store (#27267) --- packages/app/src/context/global-sync/child-store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index 0c4c68b7f..08299b301 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -209,7 +209,8 @@ export function createChildStoreManager(input: { sessionTotal: 0, session_status: {}, session_working(id: string) { - return this.session_status[id]?.type !== "idle" + const type = this.session_status[id]?.type + return (type ?? "idle") !== "idle" }, session_diff: {}, todo: {}, From e9a29e4908fa1d28213e5b4788611e9cde1a92f2 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 12:25:04 +0530 Subject: [PATCH 233/378] fix(storage): type not found errors (#27265) --- .../httpapi/handlers/session-errors.ts | 6 ++--- .../instance/httpapi/middleware/error.ts | 5 ++++- packages/opencode/src/session/session.ts | 2 +- packages/opencode/src/storage/storage.ts | 20 ++++++++++++----- .../test/session/messages-pagination.test.ts | 22 ++++++++++++++++--- .../opencode/test/storage/storage.test.ts | 17 ++++++++------ 6 files changed, 51 insertions(+), 21 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts index 98ac2b9ad..0fef2e776 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts @@ -2,8 +2,6 @@ import type { NotFoundError as StorageNotFoundError } from "@/storage/storage" import { Effect } from "effect" import * as ApiError from "../errors" -type StorageNotFound = InstanceType - -export function mapStorageNotFound(self: Effect.Effect) { - return self.pipe(Effect.mapError((error) => ApiError.notFound(error.data.message))) +export function mapStorageNotFound(self: Effect.Effect) { + return self.pipe(Effect.mapError((error) => ApiError.notFound(error.message))) } diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index 585f4bd18..c9d4871b0 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -24,11 +24,14 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) const error = defect.defect log.error("failed", { error, cause: Cause.pretty(cause) }) + if (error instanceof NotFoundError) { + return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 404 })) + } + if (error instanceof NamedError) { return Effect.succeed( HttpServerResponse.jsonUnsafe(error.toObject(), { status: iife(() => { - if (error instanceof NotFoundError) return 404 if (error instanceof Provider.ModelNotFoundError) return 400 if (error.name === "ProviderAuthValidationFailed") return 400 if (error.name.startsWith("Worktree")) return 400 diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 5e574d991..0913b8568 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -448,7 +448,7 @@ export class BusyError extends Error { } } -export type NotFound = InstanceType +export type NotFound = NotFoundError export interface Interface { readonly list: (input?: ListInput) => Effect.Effect diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts index e1f5f681b..ee2ee45f8 100644 --- a/packages/opencode/src/storage/storage.ts +++ b/packages/opencode/src/storage/storage.ts @@ -1,7 +1,6 @@ import * as Log from "@opencode-ai/core/util/log" import path from "path" import { Global } from "@opencode-ai/core/global" -import { NamedError } from "@opencode-ai/core/util/error" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect" import { NonNegativeInt } from "@opencode-ai/core/schema" @@ -15,11 +14,22 @@ type Migration = ( git: Git.Interface, ) => Effect.Effect -export const NotFoundError = NamedError.create("NotFoundError", { +export class NotFoundError extends Schema.TaggedErrorClass()("NotFoundError", { message: Schema.String, -}) +}) { + static isInstance(input: unknown): input is NotFoundError { + return input instanceof NotFoundError + } + + toObject() { + return { + name: "NotFoundError" as const, + data: { message: this.message }, + } + } +} -export type Error = AppFileSystem.Error | InstanceType +export type Error = AppFileSystem.Error | NotFoundError const RootFile = Schema.Struct({ path: Schema.optional( @@ -245,7 +255,7 @@ export const layer = Layer.effect( }), ) - const fail = (target: string): Effect.Effect> => + const fail = (target: string): Effect.Effect => Effect.fail(new NotFoundError({ message: `Resource not found: ${target}` })) const wrap =
(target: string, body: Effect.Effect) => diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index 09e8d7b42..5585aa0b0 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -4,6 +4,7 @@ import { Session as SessionNs } from "@/session/session" import { MessageV2 } from "../../src/session/message-v2" import { MessageID, PartID, type SessionID } from "../../src/session/schema" import { ModelID, ProviderID } from "../../src/provider/schema" +import { NotFoundError } from "@/storage/storage" import * as Log from "@opencode-ai/core/util/log" import { testEffect } from "../lib/effect" @@ -11,6 +12,20 @@ void Log.init({ print: false }) const it = testEffect(SessionNs.defaultLayer) +function expectNotFound(fn: () => unknown, message: string) { + let thrown: unknown + try { + fn() + } catch (error) { + thrown = error + } + expect(thrown).toBeInstanceOf(NotFoundError) + if (thrown instanceof NotFoundError) { + expect(thrown._tag).toBe("NotFoundError") + expect(thrown.message).toBe(message) + } +} + const withSession = ( fn: (input: { session: SessionNs.Interface; sessionID: SessionID }) => Effect.Effect, ) => @@ -186,7 +201,7 @@ describe("MessageV2.page", () => { it.instance("throws NotFoundError for non-existent session", () => Effect.gen(function* () { const fake = "non-existent-session" as SessionID - expect(() => MessageV2.page({ sessionID: fake, limit: 10 })).toThrow("NotFoundError") + expectNotFound(() => MessageV2.page({ sessionID: fake, limit: 10 }), `Session not found: ${fake}`) }), ) @@ -471,7 +486,8 @@ describe("MessageV2.get", () => { it.instance("throws NotFoundError for non-existent message", () => withSession(({ sessionID }) => Effect.gen(function* () { - expect(() => MessageV2.get({ sessionID, messageID: MessageID.ascending() })).toThrow("NotFoundError") + const messageID = MessageID.ascending() + expectNotFound(() => MessageV2.get({ sessionID, messageID }), `Message not found: ${messageID}`) }), ), ) @@ -483,7 +499,7 @@ describe("MessageV2.get", () => { const b = yield* session.create({}) const [id] = yield* fill(a.id, 1) - expect(() => MessageV2.get({ sessionID: b.id, messageID: id })).toThrow("NotFoundError") + expectNotFound(() => MessageV2.get({ sessionID: b.id, messageID: id }), `Message not found: ${id}`) const result = MessageV2.get({ sessionID: a.id, messageID: id }) expect(result.info.id).toBe(id) diff --git a/packages/opencode/test/storage/storage.test.ts b/packages/opencode/test/storage/storage.test.ts index f0aff4ba7..d0fe5dd34 100644 --- a/packages/opencode/test/storage/storage.test.ts +++ b/packages/opencode/test/storage/storage.test.ts @@ -74,20 +74,23 @@ describe("Storage", () => { it.live("maps missing reads to NotFoundError", () => Effect.gen(function* () { const { root, svc } = yield* scope() - const exit = yield* svc.read([...root, "missing", "value"]).pipe(Effect.exit) - expect(Exit.isFailure(exit)).toBe(true) + const error = yield* Effect.flip(svc.read([...root, "missing", "value"])) + expect(error).toBeInstanceOf(Storage.NotFoundError) + expect(error._tag).toBe("NotFoundError") + expect(error.message).toContain(path.join(...root, "missing", "value") + ".json") }), ) it.live("update on missing key throws NotFoundError", () => Effect.gen(function* () { const { root, svc } = yield* scope() - const exit = yield* svc - .update<{ value: number }>([...root, "missing", "key"], (draft) => { + const error = yield* Effect.flip( + svc.update<{ value: number }>([...root, "missing", "key"], (draft) => { draft.value += 1 - }) - .pipe(Effect.exit) - expect(Exit.isFailure(exit)).toBe(true) + }), + ) + expect(error).toBeInstanceOf(Storage.NotFoundError) + expect(error._tag).toBe("NotFoundError") }), ) From fed043a1ade7e51c5e8ac367bf56661f812becb5 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 13:04:07 +0530 Subject: [PATCH 234/378] fix(session): add typed message lookup wrappers (#27269) --- .../instance/httpapi/handlers/session.ts | 18 +++++++--------- packages/opencode/src/session/message-v2.ts | 21 +++++++++++++++++++ .../test/session/messages-pagination.test.ts | 20 ++++++++++++++++++ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index daacfec63..80bff42bc 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -14,7 +14,6 @@ import { SessionStatus } from "@/session/status" import { SessionSummary } from "@/session/summary" import { Todo } from "@/session/todo" import { MessageID, PartID, SessionID } from "@/session/schema" -import { NotFoundError } from "@/storage/storage" import { NamedError } from "@opencode-ai/core/util/error" import { Cause, Effect, Option, Schema, Scope } from "effect" import * as Stream from "effect/Stream" @@ -105,11 +104,13 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", return yield* session.messages({ sessionID: ctx.params.sessionID }) } - const page = MessageV2.page({ - sessionID: ctx.params.sessionID, - limit: ctx.query.limit, - before: ctx.query.before, - }) + const page = yield* SessionError.mapStorageNotFound( + MessageV2.pageEffect({ + sessionID: ctx.params.sessionID, + limit: ctx.query.limit, + before: ctx.query.before, + }), + ) if (!page.cursor) return page.items const request = yield* HttpServerRequest.HttpServerRequest @@ -131,10 +132,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID; messageID: MessageID } }) { return yield* SessionError.mapStorageNotFound( - Effect.try({ - try: () => MessageV2.get({ sessionID: ctx.params.sessionID, messageID: ctx.params.messageID }), - catch: (error) => error, - }).pipe(Effect.catch((error) => (NotFoundError.isInstance(error) ? Effect.fail(error) : Effect.die(error)))), + MessageV2.getEffect({ sessionID: ctx.params.sessionID, messageID: ctx.params.messageID }), ) }) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index e6ee40e95..10754298b 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -956,6 +956,17 @@ export function page(input: { sessionID: SessionID; limit: number; before?: stri } } +export const pageEffect = Effect.fn("MessageV2.pageEffect")(function* (input: { + sessionID: SessionID + limit: number + before?: string +}) { + return yield* Effect.try({ + try: () => page(input), + catch: (error) => error, + }).pipe(Effect.catch((error) => (NotFoundError.isInstance(error) ? Effect.fail(error) : Effect.die(error)))) +}) + export function* stream(sessionID: SessionID) { const size = 50 let before: string | undefined @@ -1000,6 +1011,16 @@ export function get(input: { sessionID: SessionID; messageID: MessageID }): With } } +export const getEffect = Effect.fn("MessageV2.getEffect")(function* (input: { + sessionID: SessionID + messageID: MessageID +}) { + return yield* Effect.try({ + try: () => get(input), + catch: (error) => error, + }).pipe(Effect.catch((error) => (NotFoundError.isInstance(error) ? Effect.fail(error) : Effect.die(error)))) +}) + export function filterCompacted(msgs: Iterable) { const result = [] as WithParts[] const completed = new Set() diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index 5585aa0b0..8332bb9b3 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -205,6 +205,15 @@ describe("MessageV2.page", () => { }), ) + it.instance("fails pageEffect with NotFoundError for non-existent session", () => + Effect.gen(function* () { + const fake = "non-existent-session" as SessionID + const error = yield* Effect.flip(MessageV2.pageEffect({ sessionID: fake, limit: 10 })) + expect(error).toBeInstanceOf(NotFoundError) + expect(error.message).toBe(`Session not found: ${fake}`) + }), + ) + it.instance("handles exact limit boundary", () => withSession(({ sessionID }) => Effect.gen(function* () { @@ -492,6 +501,17 @@ describe("MessageV2.get", () => { ), ) + it.instance("fails getEffect with NotFoundError for non-existent message", () => + withSession(({ sessionID }) => + Effect.gen(function* () { + const messageID = MessageID.ascending() + const error = yield* Effect.flip(MessageV2.getEffect({ sessionID, messageID })) + expect(error).toBeInstanceOf(NotFoundError) + expect(error.message).toBe(`Message not found: ${messageID}`) + }), + ), + ) + it.instance("scopes by session id", () => Effect.gen(function* () { const session = yield* SessionNs.Service From f01c6b3e37da1ee47dc506d4daf530e23c552e68 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 13:22:12 +0530 Subject: [PATCH 235/378] fix(session): type message list not found errors (#27275) --- packages/opencode/src/cli/cmd/stats.ts | 5 +- .../instance/httpapi/handlers/session.ts | 4 +- packages/opencode/src/session/compaction.ts | 4 +- packages/opencode/src/session/prompt.ts | 8 ++-- packages/opencode/src/session/revert.ts | 4 +- packages/opencode/src/session/session.ts | 42 ++++++++++++----- packages/opencode/src/session/summary.ts | 2 +- .../test/server/httpapi-exercise/runner.ts | 3 +- .../test/session/messages-pagination.test.ts | 46 ++++++++++++++++++- 9 files changed, 95 insertions(+), 23 deletions(-) diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts index 3dadea9dd..7dc182260 100644 --- a/packages/opencode/src/cli/cmd/stats.ts +++ b/packages/opencode/src/cli/cmd/stats.ts @@ -1,6 +1,7 @@ import { Effect } from "effect" import { effectCmd } from "../effect-cmd" import { Session } from "@/session/session" +import { NotFoundError } from "@/storage/storage" import { Database } from "@/storage/db" import { SessionTable } from "../../session/session.sql" import { Project } from "@/project/project" @@ -162,7 +163,9 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* ( filteredSessions, (session) => Effect.gen(function* () { - const messages = yield* svc.messages({ sessionID: session.id }) + const messages = yield* svc.messages({ sessionID: session.id }).pipe( + Effect.catchIf(NotFoundError.isInstance, () => Effect.succeed([])), + ) const sessionCost = session.cost ?? 0 const sessionTokens = session.tokens ?? { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 80bff42bc..ebd0fcd4d 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -101,7 +101,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", } yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID)) if (ctx.query.limit === undefined || ctx.query.limit === 0) { - return yield* session.messages({ sessionID: ctx.params.sessionID }) + return yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID })) } const page = yield* SessionError.mapStorageNotFound( @@ -250,7 +250,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", payload: typeof SummarizePayload.Type }) { yield* revertSvc.cleanup(yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID))) - const messages = yield* session.messages({ sessionID: ctx.params.sessionID }) + const messages = yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID })) const defaultAgent = yield* agentSvc.defaultAgent() const currentAgent = messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 4eafbdf74..f3c160fe7 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -565,7 +565,9 @@ export const layer: Layer.Layer< if (processor.message.error) return "stop" if (result === "continue") { const summary = summaryText( - (yield* session.messages({ sessionID: input.sessionID })).find((item) => item.info.id === msg.id) ?? { + (yield* session.messages({ sessionID: input.sessionID }).pipe(Effect.orDie)).find( + (item) => item.info.id === msg.id, + ) ?? { info: msg, parts: [], }, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index b89561d5d..8656050fa 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1077,7 +1077,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the ...(current.model.variant && current.model.variant !== "default" ? { variant: current.model.variant } : {}), } } - const match = yield* sessions.findMessage(sessionID, (m) => m.info.role === "user" && !!m.info.model) + const match = yield* sessions + .findMessage(sessionID, (m) => m.info.role === "user" && !!m.info.model) + .pipe(Effect.orDie) if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model return yield* provider.defaultModel() }) @@ -1615,9 +1617,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the ) const lastAssistant = Effect.fnUntraced(function* (sessionID: SessionID) { - const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user") + const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user").pipe(Effect.orDie) if (Option.isSome(match)) return match.value - const msgs = yield* sessions.messages({ sessionID, limit: 1 }) + const msgs = yield* sessions.messages({ sessionID, limit: 1 }).pipe(Effect.orDie) if (msgs.length > 0) return msgs[0] throw new Error("Impossible") }) diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index ef9089c94..8ba7b265b 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -40,7 +40,7 @@ export const layer = Layer.effect( const revert = Effect.fn("SessionRevert.revert")(function* (input: RevertInput) { yield* state.assertNotBusy(input.sessionID) - const all = yield* sessions.messages({ sessionID: input.sessionID }) + const all = yield* sessions.messages({ sessionID: input.sessionID }).pipe(Effect.orDie) let lastUser: MessageV2.User | undefined const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie) @@ -103,7 +103,7 @@ export const layer = Layer.effect( const cleanup = Effect.fn("SessionRevert.cleanup")(function* (session: Session.Info) { if (!session.revert) return const sessionID = session.id - const msgs = yield* sessions.messages({ sessionID }) + const msgs = yield* sessions.messages({ sessionID }).pipe(Effect.orDie) const messageID = session.revert.messageID const remove = [] as MessageV2.WithParts[] let target: MessageV2.WithParts | undefined diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 0913b8568..d82b2369e 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -474,7 +474,7 @@ export interface Interface { readonly clearRevert: (sessionID: SessionID) => Effect.Effect readonly setSummary: (input: { sessionID: SessionID; summary: Info["summary"] }) => Effect.Effect readonly diff: (sessionID: SessionID) => Effect.Effect - readonly messages: (input: { sessionID: SessionID; limit?: number }) => Effect.Effect + readonly messages: (input: { sessionID: SessionID; limit?: number }) => Effect.Effect readonly children: (parentID: SessionID) => Effect.Effect readonly remove: (sessionID: SessionID) => Effect.Effect readonly updateMessage: (msg: T) => Effect.Effect @@ -497,7 +497,7 @@ export interface Interface { readonly findMessage: ( sessionID: SessionID, predicate: (msg: MessageV2.WithParts) => boolean, - ) => Effect.Effect> + ) => Effect.Effect, NotFound> } export class Service extends Context.Service()("@opencode/Session") {} @@ -757,11 +757,25 @@ export const layer: Layer.Layer [])) }) - const messages = Effect.fn("Session.messages")(function* (input: { sessionID: SessionID; limit?: number }) { + const messages: Interface["messages"] = Effect.fn("Session.messages")(function* (input) { if (input.limit) { - return MessageV2.page({ sessionID: input.sessionID, limit: input.limit }).items + return (yield* MessageV2.pageEffect({ sessionID: input.sessionID, limit: input.limit })).items } - return Array.from(MessageV2.stream(input.sessionID)).reverse() + + const size = 50 + const result = [] as MessageV2.WithParts[] + let before: string | undefined + while (true) { + const page = yield* MessageV2.pageEffect({ sessionID: input.sessionID, limit: size, before }) + if (page.items.length === 0) break + for (let i = page.items.length - 1; i >= 0; i--) { + const item = page.items[i] + if (item) result.push(item) + } + if (!page.more || !page.cursor) break + before = page.cursor + } + return result.reverse() }) const removeMessage = Effect.fn("Session.removeMessage")(function* (input: { @@ -799,12 +813,18 @@ export const layer: Layer.Layer boolean, - ) { - for (const item of MessageV2.stream(sessionID)) { - if (predicate(item)) return Option.some(item) + const findMessage: Interface["findMessage"] = Effect.fn("Session.findMessage")(function* (sessionID, predicate) { + const size = 50 + let before: string | undefined + while (true) { + const page = yield* MessageV2.pageEffect({ sessionID, limit: size, before }) + if (page.items.length === 0) break + for (let i = page.items.length - 1; i >= 0; i--) { + const item = page.items[i] + if (item && predicate(item)) return Option.some(item) + } + if (!page.more || !page.cursor) break + before = page.cursor } return Option.none() }) diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index c03fc3269..aa4b8719b 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -102,7 +102,7 @@ export const layer = Layer.effect( sessionID: SessionID messageID: MessageID }) { - const all = yield* sessions.messages({ sessionID: input.sessionID }) + const all = yield* sessions.messages({ sessionID: input.sessionID }).pipe(Effect.orDie) if (!all.length) return const diffs = yield* computeDiff({ messages: all }) diff --git a/packages/opencode/test/server/httpapi-exercise/runner.ts b/packages/opencode/test/server/httpapi-exercise/runner.ts index bc246dbed..7ab5ccf99 100644 --- a/packages/opencode/test/server/httpapi-exercise/runner.ts +++ b/packages/opencode/test/server/httpapi-exercise/runner.ts @@ -168,7 +168,8 @@ function withContext( ) return { info, part } }), - messages: (sessionID) => run(modules.Session.Service.use((svc) => svc.messages({ sessionID }))), + messages: (sessionID) => + run(modules.Session.Service.use((svc) => svc.messages({ sessionID }).pipe(Effect.orDie))), todos: (sessionID, todos) => run(modules.Todo.Service.use((svc) => svc.update({ sessionID, todos }))), worktree: (input) => run(modules.Worktree.Service.use((svc) => svc.create(input))), worktreeRemove: (directory) => diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index 8332bb9b3..247f95d28 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { Effect } from "effect" +import { Effect, Option } from "effect" import { Session as SessionNs } from "@/session/session" import { MessageV2 } from "../../src/session/message-v2" import { MessageID, PartID, type SessionID } from "../../src/session/schema" @@ -582,6 +582,50 @@ describe("MessageV2.get", () => { ) }) +describe("Session.messages", () => { + it.instance("returns all messages in chronological order across pages", () => + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 55) + const result = yield* session.messages({ sessionID }) + expect(result.map((item) => item.info.id)).toEqual(ids) + }), + ), + ) + + it.instance("fails with NotFoundError for non-existent session", () => + Effect.gen(function* () { + const session = yield* SessionNs.Service + const fake = "non-existent-session" as SessionID + const error = yield* Effect.flip(session.messages({ sessionID: fake })) + expect(error).toBeInstanceOf(NotFoundError) + expect(error.message).toBe(`Session not found: ${fake}`) + }), + ) +}) + +describe("Session.findMessage", () => { + it.instance("searches newest-first", () => + withSession(({ session, sessionID }) => + Effect.gen(function* () { + const ids = yield* fill(sessionID, 3) + const result = yield* session.findMessage(sessionID, () => true) + expect(Option.isSome(result) ? result.value.info.id : undefined).toBe(ids.at(-1)) + }), + ), + ) + + it.instance("fails with NotFoundError for non-existent session", () => + Effect.gen(function* () { + const session = yield* SessionNs.Service + const fake = "non-existent-session" as SessionID + const error = yield* Effect.flip(session.findMessage(fake, () => true)) + expect(error).toBeInstanceOf(NotFoundError) + expect(error.message).toBe(`Session not found: ${fake}`) + }), + ) +}) + describe("MessageV2.filterCompacted", () => { it.instance("returns all messages when no compaction", () => withSession(({ sessionID }) => From e5af7ab99aa2c959a1ee10238bf17e6b7767eb02 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 07:53:28 +0000 Subject: [PATCH 236/378] chore: generate --- packages/opencode/src/cli/cmd/stats.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts index 7dc182260..7ee16c2e2 100644 --- a/packages/opencode/src/cli/cmd/stats.ts +++ b/packages/opencode/src/cli/cmd/stats.ts @@ -163,9 +163,9 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* ( filteredSessions, (session) => Effect.gen(function* () { - const messages = yield* svc.messages({ sessionID: session.id }).pipe( - Effect.catchIf(NotFoundError.isInstance, () => Effect.succeed([])), - ) + const messages = yield* svc + .messages({ sessionID: session.id }) + .pipe(Effect.catchIf(NotFoundError.isInstance, () => Effect.succeed([]))) const sessionCost = session.cost ?? 0 const sessionTokens = session.tokens ?? { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } From 3a810fcb9a681afd32ea99e5c9acd108a3e6c4ec Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 13 May 2026 17:57:13 +1000 Subject: [PATCH 237/378] perf(ui): render icons through an svg sprite (#26950) --- packages/ui/src/components/icon.tsx | 48 +++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 2e4d1f53b..7bd461f11 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -1,4 +1,4 @@ -import { splitProps, type ComponentProps } from "solid-js" +import { onMount, splitProps, type ComponentProps } from "solid-js" const icons = { "align-right": ``, @@ -105,6 +105,41 @@ const icons = { "arrow-undo-down": ``, } +const spriteID = "opencode-icon-sprite" +const symbol = (name: keyof typeof icons) => `opencode-icon-${name}` +let spriteInserted = false + +function viewBox(name: keyof typeof icons) { + return name === "magnifying-glass" || name === "arrow-undo-down" ? "0 0 16 16" : "0 0 20 20" +} + +function ensureSprite() { + if (spriteInserted) return + if (typeof document === "undefined") return + if (document.getElementById(spriteID)) { + spriteInserted = true + return + } + const body = document.body as HTMLElement | null + if (!body) return + + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") + svg.id = spriteID + svg.setAttribute("aria-hidden", "true") + svg.setAttribute("width", "0") + svg.setAttribute("height", "0") + svg.style.position = "absolute" + svg.style.overflow = "hidden" + svg.innerHTML = Object.entries(icons) + .map(([name, path]) => { + const key = name as keyof typeof icons + return `${path}` + }) + .join("") + body.insertBefore(svg, body.firstChild) + spriteInserted = true +} + export interface IconProps extends ComponentProps<"svg"> { name: keyof typeof icons size?: "small" | "normal" | "medium" | "large" @@ -112,8 +147,8 @@ export interface IconProps extends ComponentProps<"svg"> { export function Icon(props: IconProps) { const [local, others] = splitProps(props, ["name", "size", "class", "classList"]) - const viewBox = () => - local.name === "magnifying-glass" || local.name === "arrow-undo-down" ? "0 0 16 16" : "0 0 20 20" + onMount(ensureSprite) + return (
+ > + +
) } From 596f241db55f9f21d3c893e7cef135586e19cafa Mon Sep 17 00:00:00 2001 From: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Date: Wed, 13 May 2026 10:23:10 +0200 Subject: [PATCH 238/378] fix(app): enhance error handling by unwrapping SDK-wrapped errors in formatServerError (#27061) --- packages/app/src/utils/server-errors.test.ts | 13 +++++++++++++ packages/app/src/utils/server-errors.ts | 12 ++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/app/src/utils/server-errors.test.ts b/packages/app/src/utils/server-errors.test.ts index 1f53bb8cf..84f7c07d6 100644 --- a/packages/app/src/utils/server-errors.test.ts +++ b/packages/app/src/utils/server-errors.test.ts @@ -128,4 +128,17 @@ describe("formatServerError", () => { ["Modelo nao encontrado: x/y", "Voce quis dizer: x/y2, x/y3", "Revise provider/model no config"].join("\n"), ) }) + + test("unwraps SDK-wrapped errors from cause.body", () => { + const body = { + name: "ConfigInvalidError", + data: { + message: "Missing host", + }, + } satisfies ConfigInvalidError + + const wrapped = new Error("ConfigInvalidError", { cause: { body, status: 400 } }) + + expect(formatServerError(wrapped, language.t)).toBe("Arquivo de config em config invalido: Missing host") + }) }) diff --git a/packages/app/src/utils/server-errors.ts b/packages/app/src/utils/server-errors.ts index 2c3a8c54d..8a8db1781 100644 --- a/packages/app/src/utils/server-errors.ts +++ b/packages/app/src/utils/server-errors.ts @@ -26,14 +26,22 @@ function tr(translator: Translator | undefined, key: string, text: string, vars? } export function formatServerError(error: unknown, translate?: Translator, fallback?: string) { - if (isConfigInvalidErrorLike(error)) return parseReadableConfigInvalidError(error, translate) - if (isProviderModelNotFoundErrorLike(error)) return parseReadableProviderModelNotFoundError(error, translate) + const unwrapped = unwrapNamedError(error) + if (isConfigInvalidErrorLike(unwrapped)) return parseReadableConfigInvalidError(unwrapped, translate) + if (isProviderModelNotFoundErrorLike(unwrapped)) return parseReadableProviderModelNotFoundError(unwrapped, translate) if (error instanceof Error && error.message) return error.message if (typeof error === "string" && error) return error if (fallback) return fallback return tr(translate, "error.chain.unknown", "Unknown error") } +function unwrapNamedError(error: unknown): unknown { + if (error instanceof Error && error.cause && typeof error.cause === "object" && "body" in error.cause) { + return (error.cause as Record).body + } + return error +} + function isConfigInvalidErrorLike(error: unknown): error is ConfigInvalidError { if (typeof error !== "object" || error === null) return false const o = error as Record From b0dc8e4638fef6c96665d68d2268a14443e7f05b Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 14:12:51 +0530 Subject: [PATCH 239/378] fix(session): use typed message reads in tools (#27280) --- packages/opencode/src/share/share-next.ts | 2 +- packages/opencode/src/tool/plan.ts | 14 +++++--------- packages/opencode/src/tool/task.ts | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 384027436..a386211dc 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -272,7 +272,7 @@ export const layer = Layer.effect( 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 messages = yield* session.messages({ sessionID }) const models = yield* Effect.forEach( Array.from( new Map( diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts index d5195376b..af206f66a 100644 --- a/packages/opencode/src/tool/plan.ts +++ b/packages/opencode/src/tool/plan.ts @@ -6,16 +6,9 @@ import { Session } from "@/session/session" import { MessageV2 } from "../session/message-v2" import { Provider } from "@/provider/provider" import { InstanceState } from "@/effect/instance-state" -import { type SessionID, MessageID, PartID } from "../session/schema" +import { MessageID, PartID } from "../session/schema" import EXIT_DESCRIPTION from "./plan-exit.txt" -function getLastModel(sessionID: SessionID) { - for (const item of MessageV2.stream(sessionID)) { - if (item.info.role === "user" && item.info.model) return item.info.model - } - return undefined -} - export const Parameters = Schema.Struct({}) export const PlanExitTool = Tool.define( @@ -51,7 +44,10 @@ export const PlanExitTool = Tool.define( if (answers[0]?.[0] === "No") yield* new Question.RejectedError() - const model = getLastModel(ctx.sessionID) ?? (yield* provider.defaultModel()) + const messages = yield* session.messages({ sessionID: ctx.sessionID }).pipe(Effect.orDie) + const lastUser = messages.findLast((item) => item.info.role === "user" && item.info.model) + const model = + lastUser?.info.role === "user" && lastUser.info.model ? lastUser.info.model : yield* provider.defaultModel() const msg: MessageV2.User = { id: MessageID.ascending(), diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index c4d5bf7f4..d3572c1c4 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -86,7 +86,7 @@ export const TaskTool = Tool.define( ], })) - const msg = yield* Effect.sync(() => MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID })) + const msg = yield* MessageV2.getEffect({ sessionID: ctx.sessionID, messageID: ctx.messageID }).pipe(Effect.orDie) if (msg.info.role !== "assistant") return yield* Effect.fail(new Error("Not an assistant message")) const model = next.model ?? { From 4b041716fcd1809518df84eb9f3f11a4f94439ed Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 14:15:53 +0530 Subject: [PATCH 240/378] fix(server): remove storage not found defect fallback (#27287) --- .../instance/httpapi/middleware/error.ts | 5 ----- .../server/httpapi-error-middleware.test.ts | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index c9d4871b0..087d5ee85 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -1,6 +1,5 @@ import { Provider } from "@/provider/provider" import { Session } from "@/session/session" -import { NotFoundError } from "@/storage/storage" import { iife } from "@/util/iife" import { NamedError } from "@opencode-ai/core/util/error" import * as Log from "@opencode-ai/core/util/log" @@ -24,10 +23,6 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) const error = defect.defect log.error("failed", { error, cause: Cause.pretty(cause) }) - if (error instanceof NotFoundError) { - return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 404 })) - } - if (error instanceof NamedError) { return Effect.succeed( HttpServerResponse.jsonUnsafe(error.toObject(), { diff --git a/packages/opencode/test/server/httpapi-error-middleware.test.ts b/packages/opencode/test/server/httpapi-error-middleware.test.ts index a07e31382..f53a9e887 100644 --- a/packages/opencode/test/server/httpapi-error-middleware.test.ts +++ b/packages/opencode/test/server/httpapi-error-middleware.test.ts @@ -3,6 +3,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http" import { errorLayer } from "../../src/server/routes/instance/httpapi/middleware/error" +import { NotFoundError } from "../../src/storage/storage" import { testEffect } from "../lib/effect" const it = testEffect(Layer.mergeAll(NodeHttpServer.layerTest, NodeServices.layer)) @@ -27,4 +28,23 @@ describe("HttpApi error middleware", () => { expect(JSON.stringify(body)).not.toContain("secret stack marker") }), ) + + it.live("does not map storage not-found defects to 404", () => + Effect.gen(function* () { + yield* HttpRouter.add( + "GET", + "/missing", + Effect.die(new NotFoundError({ message: "Resource not found: secret" })), + ).pipe(Layer.provide(errorLayer), HttpRouter.serve, Layer.build) + + const response = yield* HttpClientRequest.get("/missing").pipe(HttpClient.execute) + const body = yield* response.json + + expect(response.status).toBe(500) + expect(body).toEqual({ + name: "UnknownError", + data: { message: "Unexpected server error. Check server logs for details." }, + }) + }), + ) }) From ccf93f3523393b5226a1172ddaa85ac07a5c4b6d Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 14:40:12 +0530 Subject: [PATCH 241/378] fix(session): make message reads effectful (#27291) --- .../instance/httpapi/handlers/session.ts | 4 +- packages/opencode/src/session/message-v2.ts | 41 +++----- packages/opencode/src/session/session.ts | 6 +- packages/opencode/src/tool/task.ts | 2 +- .../test/session/messages-pagination.test.ts | 94 +++++++------------ .../test/session/processor-effect.test.ts | 4 +- packages/opencode/test/session/prompt.test.ts | 8 +- 7 files changed, 60 insertions(+), 99 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index ebd0fcd4d..40444385f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -105,7 +105,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", } const page = yield* SessionError.mapStorageNotFound( - MessageV2.pageEffect({ + MessageV2.page({ sessionID: ctx.params.sessionID, limit: ctx.query.limit, before: ctx.query.before, @@ -132,7 +132,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID; messageID: MessageID } }) { return yield* SessionError.mapStorageNotFound( - MessageV2.getEffect({ sessionID: ctx.params.sessionID, messageID: ctx.params.messageID }), + MessageV2.get({ sessionID: ctx.params.sessionID, messageID: ctx.params.messageID }), ) }) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 10754298b..869ef979f 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -919,7 +919,11 @@ export function toModelMessages( return Effect.runPromise(toModelMessagesEffect(input, model, options).pipe(Effect.provide(EffectLogger.layer))) } -export function page(input: { sessionID: SessionID; limit: number; before?: string }) { +export const page = Effect.fn("MessageV2.page")(function* (input: { + sessionID: SessionID + limit: number + before?: string +}) { const before = input.before ? cursor.decode(input.before) : undefined const where = before ? and(eq(MessageTable.session_id, input.sessionID), older(before)) @@ -937,7 +941,7 @@ export function page(input: { sessionID: SessionID; limit: number; before?: stri const row = Database.use((db) => db.select({ id: SessionTable.id }).from(SessionTable).where(eq(SessionTable.id, input.sessionID)).get(), ) - if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` }) + if (!row) return yield* new NotFoundError({ message: `Session not found: ${input.sessionID}` }) return { items: [] as WithParts[], more: false, @@ -954,24 +958,19 @@ export function page(input: { sessionID: SessionID; limit: number; before?: stri more, cursor: more && tail ? cursor.encode({ id: tail.id, time: tail.time_created }) : undefined, } -} - -export const pageEffect = Effect.fn("MessageV2.pageEffect")(function* (input: { - sessionID: SessionID - limit: number - before?: string -}) { - return yield* Effect.try({ - try: () => page(input), - catch: (error) => error, - }).pipe(Effect.catch((error) => (NotFoundError.isInstance(error) ? Effect.fail(error) : Effect.die(error)))) }) export function* stream(sessionID: SessionID) { const size = 50 let before: string | undefined while (true) { - const next = page({ sessionID, limit: size, before }) + const next = Effect.runSync( + page({ sessionID, limit: size, before }).pipe( + Effect.catchIf(NotFoundError.isInstance, () => + Effect.succeed({ items: [] as WithParts[], more: false, cursor: undefined }), + ), + ), + ) if (next.items.length === 0) break for (let i = next.items.length - 1; i >= 0; i--) { yield next.items[i] @@ -996,7 +995,7 @@ export function parts(message_id: MessageID) { ) } -export function get(input: { sessionID: SessionID; messageID: MessageID }): WithParts { +export const get = Effect.fn("MessageV2.get")(function* (input: { sessionID: SessionID; messageID: MessageID }) { const row = Database.use((db) => db .select() @@ -1004,21 +1003,11 @@ export function get(input: { sessionID: SessionID; messageID: MessageID }): With .where(and(eq(MessageTable.id, input.messageID), eq(MessageTable.session_id, input.sessionID))) .get(), ) - if (!row) throw new NotFoundError({ message: `Message not found: ${input.messageID}` }) + if (!row) return yield* new NotFoundError({ message: `Message not found: ${input.messageID}` }) return { info: info(row), parts: parts(input.messageID), } -} - -export const getEffect = Effect.fn("MessageV2.getEffect")(function* (input: { - sessionID: SessionID - messageID: MessageID -}) { - return yield* Effect.try({ - try: () => get(input), - catch: (error) => error, - }).pipe(Effect.catch((error) => (NotFoundError.isInstance(error) ? Effect.fail(error) : Effect.die(error)))) }) export function filterCompacted(msgs: Iterable) { diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index d82b2369e..df173e895 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -759,14 +759,14 @@ export const layer: Layer.Layer= 0; i--) { const item = page.items[i] @@ -817,7 +817,7 @@ export const layer: Layer.Layer= 0; i--) { const item = page.items[i] diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index d3572c1c4..09bbaca26 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -86,7 +86,7 @@ export const TaskTool = Tool.define( ], })) - const msg = yield* MessageV2.getEffect({ sessionID: ctx.sessionID, messageID: ctx.messageID }).pipe(Effect.orDie) + const msg = yield* MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID }).pipe(Effect.orDie) if (msg.info.role !== "assistant") return yield* Effect.fail(new Error("Not an assistant message")) const model = next.model ?? { diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index 247f95d28..e558d07b5 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -12,20 +12,6 @@ void Log.init({ print: false }) const it = testEffect(SessionNs.defaultLayer) -function expectNotFound(fn: () => unknown, message: string) { - let thrown: unknown - try { - fn() - } catch (error) { - thrown = error - } - expect(thrown).toBeInstanceOf(NotFoundError) - if (thrown instanceof NotFoundError) { - expect(thrown._tag).toBe("NotFoundError") - expect(thrown.message).toBe(message) - } -} - const withSession = ( fn: (input: { session: SessionNs.Interface; sessionID: SessionID }) => Effect.Effect, ) => @@ -140,12 +126,12 @@ const addCompactionPart = Effect.fn("Test.addCompactionPart")(function* ( }) describe("MessageV2.page", () => { - it.instance("returns sync result", () => + it.instance("returns page result", () => withSession(({ sessionID }) => Effect.gen(function* () { yield* fill(sessionID, 2) - const result = MessageV2.page({ sessionID, limit: 10 }) + const result = yield* MessageV2.page({ sessionID, limit: 10 }) expect(result).toBeDefined() expect(result.items).toBeArray() }), @@ -157,18 +143,18 @@ describe("MessageV2.page", () => { Effect.gen(function* () { const ids = yield* fill(sessionID, 6) - const a = MessageV2.page({ sessionID, limit: 2 }) + const a = yield* MessageV2.page({ sessionID, limit: 2 }) expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) expect(a.items.every((item) => item.parts.length === 1)).toBe(true) expect(a.more).toBe(true) expect(a.cursor).toBeTruthy() - const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + const b = yield* MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) expect(b.more).toBe(true) expect(b.cursor).toBeTruthy() - const c = MessageV2.page({ sessionID, limit: 2, before: b.cursor! }) + const c = yield* MessageV2.page({ sessionID, limit: 2, before: b.cursor! }) expect(c.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) expect(c.more).toBe(false) expect(c.cursor).toBeUndefined() @@ -181,7 +167,7 @@ describe("MessageV2.page", () => { Effect.gen(function* () { const ids = yield* fill(sessionID, 4) - const result = MessageV2.page({ sessionID, limit: 4 }) + const result = yield* MessageV2.page({ sessionID, limit: 4 }) expect(result.items.map((item) => item.info.id)).toEqual(ids) }), ), @@ -190,7 +176,7 @@ describe("MessageV2.page", () => { it.instance("returns empty items for session with no messages", () => withSession(({ sessionID }) => Effect.gen(function* () { - const result = MessageV2.page({ sessionID, limit: 10 }) + const result = yield* MessageV2.page({ sessionID, limit: 10 }) expect(result.items).toEqual([]) expect(result.more).toBe(false) expect(result.cursor).toBeUndefined() @@ -198,17 +184,10 @@ describe("MessageV2.page", () => { ), ) - it.instance("throws NotFoundError for non-existent session", () => - Effect.gen(function* () { - const fake = "non-existent-session" as SessionID - expectNotFound(() => MessageV2.page({ sessionID: fake, limit: 10 }), `Session not found: ${fake}`) - }), - ) - - it.instance("fails pageEffect with NotFoundError for non-existent session", () => + it.instance("fails with NotFoundError for non-existent session", () => Effect.gen(function* () { const fake = "non-existent-session" as SessionID - const error = yield* Effect.flip(MessageV2.pageEffect({ sessionID: fake, limit: 10 })) + const error = yield* Effect.flip(MessageV2.page({ sessionID: fake, limit: 10 })) expect(error).toBeInstanceOf(NotFoundError) expect(error.message).toBe(`Session not found: ${fake}`) }), @@ -219,7 +198,7 @@ describe("MessageV2.page", () => { Effect.gen(function* () { const ids = yield* fill(sessionID, 3) - const result = MessageV2.page({ sessionID, limit: 3 }) + const result = yield* MessageV2.page({ sessionID, limit: 3 }) expect(result.items.map((item) => item.info.id)).toEqual(ids) expect(result.more).toBe(false) expect(result.cursor).toBeUndefined() @@ -232,7 +211,7 @@ describe("MessageV2.page", () => { Effect.gen(function* () { const ids = yield* fill(sessionID, 5) - const result = MessageV2.page({ sessionID, limit: 1 }) + const result = yield* MessageV2.page({ sessionID, limit: 1 }) expect(result.items).toHaveLength(1) expect(result.items[0].info.id).toBe(ids[ids.length - 1]) expect(result.more).toBe(true) @@ -253,7 +232,7 @@ describe("MessageV2.page", () => { text: "extra", }) - const result = MessageV2.page({ sessionID, limit: 10 }) + const result = yield* MessageV2.page({ sessionID, limit: 10 }) expect(result.items).toHaveLength(1) expect(result.items[0].parts).toHaveLength(2) }), @@ -265,8 +244,8 @@ describe("MessageV2.page", () => { Effect.gen(function* () { const ids = yield* fill(sessionID, 4, (i: number) => 1000.5 + i) - const a = MessageV2.page({ sessionID, limit: 2 }) - const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + const a = yield* MessageV2.page({ sessionID, limit: 2 }) + const b = yield* MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) @@ -279,11 +258,11 @@ describe("MessageV2.page", () => { Effect.gen(function* () { const ids = yield* fill(sessionID, 4, () => 1000) - const a = MessageV2.page({ sessionID, limit: 2 }) + const a = yield* MessageV2.page({ sessionID, limit: 2 }) expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) expect(a.more).toBe(true) - const b = MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) + const b = yield* MessageV2.page({ sessionID, limit: 2, before: a.cursor! }) expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) expect(b.more).toBe(false) }), @@ -298,8 +277,8 @@ describe("MessageV2.page", () => { yield* fill(a.id, 3) yield* fill(b.id, 2) - const resultA = MessageV2.page({ sessionID: a.id, limit: 10 }) - const resultB = MessageV2.page({ sessionID: b.id, limit: 10 }) + const resultA = yield* MessageV2.page({ sessionID: a.id, limit: 10 }) + const resultB = yield* MessageV2.page({ sessionID: b.id, limit: 10 }) expect(resultA.items).toHaveLength(3) expect(resultB.items).toHaveLength(2) expect(resultA.items.every((item) => item.info.sessionID === a.id)).toBe(true) @@ -315,7 +294,7 @@ describe("MessageV2.page", () => { Effect.gen(function* () { const ids = yield* fill(sessionID, 10) - const result = MessageV2.page({ sessionID, limit: 100 }) + const result = yield* MessageV2.page({ sessionID, limit: 100 }) expect(result.items).toHaveLength(10) expect(result.items.map((item) => item.info.id)).toEqual(ids) expect(result.more).toBe(false) @@ -482,7 +461,7 @@ describe("MessageV2.get", () => { Effect.gen(function* () { const [id] = yield* fill(sessionID, 1) - const result = MessageV2.get({ sessionID, messageID: id }) + const result = yield* MessageV2.get({ sessionID, messageID: id }) expect(result.info.id).toBe(id) expect(result.info.sessionID).toBe(sessionID) expect(result.info.role).toBe("user") @@ -492,20 +471,11 @@ describe("MessageV2.get", () => { ), ) - it.instance("throws NotFoundError for non-existent message", () => + it.instance("fails with NotFoundError for non-existent message", () => withSession(({ sessionID }) => Effect.gen(function* () { const messageID = MessageID.ascending() - expectNotFound(() => MessageV2.get({ sessionID, messageID }), `Message not found: ${messageID}`) - }), - ), - ) - - it.instance("fails getEffect with NotFoundError for non-existent message", () => - withSession(({ sessionID }) => - Effect.gen(function* () { - const messageID = MessageID.ascending() - const error = yield* Effect.flip(MessageV2.getEffect({ sessionID, messageID })) + const error = yield* Effect.flip(MessageV2.get({ sessionID, messageID })) expect(error).toBeInstanceOf(NotFoundError) expect(error.message).toBe(`Message not found: ${messageID}`) }), @@ -519,8 +489,10 @@ describe("MessageV2.get", () => { const b = yield* session.create({}) const [id] = yield* fill(a.id, 1) - expectNotFound(() => MessageV2.get({ sessionID: b.id, messageID: id }), `Message not found: ${id}`) - const result = MessageV2.get({ sessionID: a.id, messageID: id }) + const error = yield* Effect.flip(MessageV2.get({ sessionID: b.id, messageID: id })) + expect(error).toBeInstanceOf(NotFoundError) + expect(error.message).toBe(`Message not found: ${id}`) + const result = yield* MessageV2.get({ sessionID: a.id, messageID: id }) expect(result.info.id).toBe(id) yield* session.remove(a.id) @@ -541,7 +513,7 @@ describe("MessageV2.get", () => { text: "extra", }) - const result = MessageV2.get({ sessionID, messageID: id }) + const result = yield* MessageV2.get({ sessionID, messageID: id }) expect(result.parts).toHaveLength(2) }), ), @@ -561,7 +533,7 @@ describe("MessageV2.get", () => { text: "response", }) - const result = MessageV2.get({ sessionID, messageID: aid }) + const result = yield* MessageV2.get({ sessionID, messageID: aid }) expect(result.info.role).toBe("assistant") expect(result.parts).toHaveLength(1) expect((result.parts[0] as MessageV2.TextPart).text).toBe("response") @@ -574,7 +546,7 @@ describe("MessageV2.get", () => { Effect.gen(function* () { const id = yield* addUser(sessionID) - const result = MessageV2.get({ sessionID, messageID: id }) + const result = yield* MessageV2.get({ sessionID, messageID: id }) expect(result.info.id).toBe(id) expect(result.parts).toEqual([]) }), @@ -1026,9 +998,9 @@ describe("MessageV2 consistency", () => { Effect.gen(function* () { yield* fill(sessionID, 3) - const paged = MessageV2.page({ sessionID, limit: 10 }) + const paged = yield* MessageV2.page({ sessionID, limit: 10 }) for (const item of paged.items) { - const got = MessageV2.get({ sessionID, messageID: item.info.id as MessageID }) + const got = yield* MessageV2.get({ sessionID, messageID: item.info.id as MessageID }) expect(got.info).toEqual(item.info) expect(got.parts).toEqual(item.parts) } @@ -1041,7 +1013,7 @@ describe("MessageV2 consistency", () => { Effect.gen(function* () { const [id] = yield* fill(sessionID, 1) - const got = MessageV2.get({ sessionID, messageID: id }) + const got = yield* MessageV2.get({ sessionID, messageID: id }) const standalone = MessageV2.parts(id) expect(got.parts).toEqual(standalone) }), @@ -1058,7 +1030,7 @@ describe("MessageV2 consistency", () => { const paged = [] as MessageV2.WithParts[] let cursor: string | undefined while (true) { - const result = MessageV2.page({ sessionID, limit: 3, before: cursor }) + const result = yield* MessageV2.page({ sessionID, limit: 3, before: cursor }) for (let i = result.items.length - 1; i >= 0; i--) { paged.push(result.items[i]) } diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index 56ff10243..fc5e73877 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -767,7 +767,7 @@ it.live("session.processor effect tests record aborted errors and idle state", ( const exit = yield* Fiber.await(run) yield* Effect.promise(() => seen.promise) - const stored = MessageV2.get({ sessionID: chat.id, messageID: msg.id }) + const stored = yield* MessageV2.get({ sessionID: chat.id, messageID: msg.id }) const state = yield* sts.get(chat.id) off() @@ -829,7 +829,7 @@ it.live("session.processor effect tests mark interruptions aborted without manua yield* Fiber.interrupt(run) const exit = yield* Fiber.await(run) - const stored = MessageV2.get({ sessionID: chat.id, messageID: msg.id }) + const stored = yield* MessageV2.get({ sessionID: chat.id, messageID: msg.id }) const state = yield* sts.get(chat.id) expect(Exit.isFailure(exit)).toBe(true) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index e7791db30..427ce17e8 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1787,7 +1787,7 @@ it.instance( if (msg.info.role !== "user") throw new Error("expected user message") - const stored = MessageV2.get({ + const stored = yield* MessageV2.get({ sessionID: session.id, messageID: msg.info.id, }) @@ -1875,7 +1875,7 @@ it.instance( parts: yield* prompt.resolvePromptParts("Use @docs for context"), }) - const stored = MessageV2.get({ sessionID: session.id, messageID: message.info.id }) + const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id }) const synthetic = stored.parts.filter( (part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true, ) @@ -1931,7 +1931,7 @@ it.instance( ], }) - const stored = MessageV2.get({ sessionID: session.id, messageID: message.info.id }) + const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id }) const synthetic = stored.parts.filter( (part): part is MessageV2.TextPart => part.type === "text" && part.synthetic === true, ) @@ -1991,7 +1991,7 @@ it.instance( parts, noReply: true, }) - const stored = MessageV2.get({ sessionID: session.id, messageID: message.info.id }) + const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id }) const textParts = stored.parts.filter((part) => part.type === "text") const hasContent = textParts.some((part) => part.text.includes("special content")) expect(hasContent).toBe(true) From 2e7cf92c8b04f16c899eb0daf693c092c3698c7a Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 15:05:30 +0530 Subject: [PATCH 242/378] fix(worktree): type expected errors (#27296) --- .../instance/httpapi/groups/experimental.ts | 23 +++- .../instance/httpapi/handlers/experimental.ts | 16 ++- .../instance/httpapi/middleware/error.ts | 1 - packages/opencode/src/worktree/index.ts | 112 ++++++++++-------- .../opencode/test/project/worktree.test.ts | 16 ++- .../test/server/httpapi-exercise/runner.ts | 2 +- .../test/server/httpapi-experimental.test.ts | 17 +++ 7 files changed, 125 insertions(+), 62 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts index 99a8a21a9..160bafb1e 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/experimental.ts @@ -54,6 +54,22 @@ export const ToolListQuery = Schema.Struct({ }) const WorktreeList = Schema.Array(Schema.String) +const WorktreeErrorName = Schema.Union([ + Schema.Literal("WorktreeNotGitError"), + Schema.Literal("WorktreeNameGenerationFailedError"), + Schema.Literal("WorktreeCreateFailedError"), + Schema.Literal("WorktreeStartCommandFailedError"), + Schema.Literal("WorktreeRemoveFailedError"), + Schema.Literal("WorktreeResetFailedError"), + Schema.Literal("WorktreeListFailedError"), +]) +export class WorktreeApiError extends Schema.ErrorClass("WorktreeError")( + { + name: WorktreeErrorName, + data: Schema.Struct({ message: Schema.String }), + }, + { httpApiStatus: 400 }, +) {} export const SessionListQuery = Schema.Struct({ ...WorkspaceRoutingQueryFields, roots: Schema.optional(QueryBoolean), @@ -141,6 +157,7 @@ export const ExperimentalApi = HttpApi.make("experimental") HttpApiEndpoint.get("worktree", ExperimentalPaths.worktree, { query: WorkspaceRoutingQuery, success: described(WorktreeList, "List of worktree directories"), + error: WorktreeApiError, }).annotateMerge( OpenApi.annotations({ identifier: "worktree.list", @@ -152,7 +169,7 @@ export const ExperimentalApi = HttpApi.make("experimental") query: WorkspaceRoutingQuery, payload: Schema.optional(Worktree.CreateInput), success: described(Worktree.Info, "Worktree created"), - error: HttpApiError.BadRequest, + error: WorktreeApiError, }).annotateMerge( OpenApi.annotations({ identifier: "worktree.create", @@ -164,7 +181,7 @@ export const ExperimentalApi = HttpApi.make("experimental") query: WorkspaceRoutingQuery, payload: Worktree.RemoveInput, success: described(Schema.Boolean, "Worktree removed"), - error: HttpApiError.BadRequest, + error: WorktreeApiError, }).annotateMerge( OpenApi.annotations({ identifier: "worktree.remove", @@ -176,7 +193,7 @@ export const ExperimentalApi = HttpApi.make("experimental") query: WorkspaceRoutingQuery, payload: Worktree.ResetInput, success: described(Schema.Boolean, "Worktree reset"), - error: HttpApiError.BadRequest, + error: WorktreeApiError, }).annotateMerge( OpenApi.annotations({ identifier: "worktree.reset", diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts index 9cf668ceb..89942ec4d 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts @@ -12,7 +12,15 @@ import { Effect, Option } from "effect" import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi" import { InstanceHttpApi } from "../api" -import { ConsoleSwitchPayload, SessionListQuery, ToolListQuery } from "../groups/experimental" +import { ConsoleSwitchPayload, SessionListQuery, ToolListQuery, WorktreeApiError } from "../groups/experimental" + +function mapWorktreeError(self: Effect.Effect) { + return self.pipe( + Effect.mapError( + (error) => new WorktreeApiError({ name: error._tag, data: { message: error.message } }), + ), + ) +} export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "experimental", (handlers) => Effect.gen(function* () { @@ -100,14 +108,14 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper const worktreeCreate = Effect.fn("ExperimentalHttpApi.worktreeCreate")(function* (ctx: { payload: Worktree.CreateInput | undefined }) { - return yield* worktreeSvc.create(ctx.payload) + return yield* mapWorktreeError(worktreeSvc.create(ctx.payload)) }) const worktreeRemove = Effect.fn("ExperimentalHttpApi.worktreeRemove")(function* (input: { payload: Worktree.RemoveInput }) { const ctx = yield* InstanceState.context - yield* worktreeSvc.remove(input.payload) + yield* mapWorktreeError(worktreeSvc.remove(input.payload)) yield* project.removeSandbox(ctx.project.id, input.payload.directory) return true }) @@ -115,7 +123,7 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper const worktreeReset = Effect.fn("ExperimentalHttpApi.worktreeReset")(function* (ctx: { payload: Worktree.ResetInput }) { - yield* worktreeSvc.reset(ctx.payload) + yield* mapWorktreeError(worktreeSvc.reset(ctx.payload)) return true }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index 087d5ee85..acc39fb1e 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -29,7 +29,6 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) status: iife(() => { if (error instanceof Provider.ModelNotFoundError) return 400 if (error.name === "ProviderAuthValidationFailed") return 400 - if (error.name.startsWith("Worktree")) return 400 return 500 }), }), diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 7d0218926..58651ddec 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -1,4 +1,3 @@ -import { NamedError } from "@opencode-ai/core/util/error" import { Global } from "@opencode-ai/core/global" import { InstanceLayer } from "@/project/instance-layer" import { InstanceStore } from "@/project/instance-store" @@ -64,33 +63,48 @@ export const ResetInput = Schema.Struct({ }).annotate({ identifier: "WorktreeResetInput" }) export type ResetInput = Schema.Schema.Type -export const NotGitError = NamedError.create("WorktreeNotGitError", { +export class NotGitError extends Schema.TaggedErrorClass()("WorktreeNotGitError", { message: Schema.String, -}) +}) {} -export const NameGenerationFailedError = NamedError.create("WorktreeNameGenerationFailedError", { - message: Schema.String, -}) +export class NameGenerationFailedError extends Schema.TaggedErrorClass()( + "WorktreeNameGenerationFailedError", + { + message: Schema.String, + }, +) {} -export const CreateFailedError = NamedError.create("WorktreeCreateFailedError", { +export class CreateFailedError extends Schema.TaggedErrorClass()("WorktreeCreateFailedError", { message: Schema.String, -}) +}) {} -export const StartCommandFailedError = NamedError.create("WorktreeStartCommandFailedError", { - message: Schema.String, -}) +export class StartCommandFailedError extends Schema.TaggedErrorClass()( + "WorktreeStartCommandFailedError", + { + message: Schema.String, + }, +) {} -export const RemoveFailedError = NamedError.create("WorktreeRemoveFailedError", { +export class RemoveFailedError extends Schema.TaggedErrorClass()("WorktreeRemoveFailedError", { message: Schema.String, -}) +}) {} -export const ResetFailedError = NamedError.create("WorktreeResetFailedError", { +export class ResetFailedError extends Schema.TaggedErrorClass()("WorktreeResetFailedError", { message: Schema.String, -}) +}) {} -export const ListFailedError = NamedError.create("WorktreeListFailedError", { +export class ListFailedError extends Schema.TaggedErrorClass()("WorktreeListFailedError", { message: Schema.String, -}) +}) {} + +export type Error = + | NotGitError + | NameGenerationFailedError + | CreateFailedError + | StartCommandFailedError + | RemoveFailedError + | ResetFailedError + | ListFailedError function slugify(input: string) { return input @@ -121,12 +135,12 @@ function failedRemoves(...chunks: string[]) { // --------------------------------------------------------------------------- export interface Interface { - readonly makeWorktreeInfo: (options?: { name?: string; detached?: boolean }) => Effect.Effect - readonly createFromInfo: (info: Info, startCommand?: string) => Effect.Effect - readonly create: (input?: CreateInput) => Effect.Effect - readonly list: () => Effect.Effect<(Omit & { branch?: string })[]> - readonly remove: (input: RemoveInput) => Effect.Effect - readonly reset: (input: ResetInput) => Effect.Effect + readonly makeWorktreeInfo: (options?: { name?: string; detached?: boolean }) => Effect.Effect + readonly createFromInfo: (info: Info, startCommand?: string) => Effect.Effect + readonly create: (input?: CreateInput) => Effect.Effect + readonly list: () => Effect.Effect<(Omit & { branch?: string })[], Error> + readonly remove: (input: RemoveInput) => Effect.Effect + readonly reset: (input: ResetInput) => Effect.Effect } export class Service extends Context.Service()("@opencode/Worktree") {} @@ -193,7 +207,7 @@ export const layer: Layer.Layer< return { name, directory, ...(branch ? { branch } : {}) } } - throw new NameGenerationFailedError({ message: "Failed to generate a unique worktree name" }) + return yield* new NameGenerationFailedError({ message: "Failed to generate a unique worktree name" }) }) const makeWorktreeInfo = Effect.fn("Worktree.makeWorktreeInfo")(function* (input?: { @@ -202,7 +216,7 @@ export const layer: Layer.Layer< }) { const ctx = yield* InstanceState.context if (ctx.project.vcs !== "git") { - throw new NotGitError({ message: "Worktrees are only supported for git projects" }) + return yield* new NotGitError({ message: "Worktrees are only supported for git projects" }) } const root = pathSvc.join(Global.Path.data, "worktree", ctx.project.id) @@ -220,7 +234,7 @@ export const layer: Layer.Layer< { cwd: ctx.worktree }, ) if (created.code !== 0) { - throw new CreateFailedError({ message: created.stderr || created.text || "Failed to create git worktree" }) + return yield* new CreateFailedError({ message: created.stderr || created.text || "Failed to create git worktree" }) } yield* project.addSandbox(ctx.project.id, info.directory).pipe(Effect.catch(() => Effect.void)) @@ -336,7 +350,7 @@ export const layer: Layer.Layer< const result = yield* git(["worktree", "list", "--porcelain"], { cwd: ctx.worktree }) if (result.code !== 0) { - throw new ListFailedError({ message: result.stderr || result.text || "Failed to read git worktrees" }) + return yield* new ListFailedError({ message: result.stderr || result.text || "Failed to read git worktrees" }) } const primary = yield* canonical(ctx.worktree) @@ -364,27 +378,27 @@ export const layer: Layer.Layer< } function cleanDirectory(target: string) { - return Effect.promise(() => - import("fs/promises") - .then((fsp) => fsp.rm(target, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })) - .catch((error) => { - const message = errorMessage(error) - throw new RemoveFailedError({ message: message || "Failed to remove git worktree directory" }) - }), - ) + return Effect.tryPromise({ + try: () => + import("fs/promises").then((fsp) => + fsp.rm(target, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }), + ), + catch: (error) => + new RemoveFailedError({ message: errorMessage(error) || "Failed to remove git worktree directory" }), + }) } const remove = Effect.fn("Worktree.remove")(function* (input: RemoveInput) { const ctx = yield* InstanceState.context if (ctx.project.vcs !== "git") { - throw new NotGitError({ message: "Worktrees are only supported for git projects" }) + return yield* new NotGitError({ message: "Worktrees are only supported for git projects" }) } const directory = yield* canonical(input.directory) const list = yield* git(["worktree", "list", "--porcelain"], { cwd: ctx.worktree }) if (list.code !== 0) { - throw new RemoveFailedError({ message: list.stderr || list.text || "Failed to read git worktrees" }) + return yield* new RemoveFailedError({ message: list.stderr || list.text || "Failed to read git worktrees" }) } const entries = parseWorktreeList(list.text) @@ -404,14 +418,14 @@ export const layer: Layer.Layer< if (removed.code !== 0) { const next = yield* git(["worktree", "list", "--porcelain"], { cwd: ctx.worktree }) if (next.code !== 0) { - throw new RemoveFailedError({ + return yield* new RemoveFailedError({ message: removed.stderr || removed.text || next.stderr || next.text || "Failed to remove git worktree", }) } const stale = yield* locateWorktree(parseWorktreeList(next.text), directory) if (stale?.path) { - throw new RemoveFailedError({ message: removed.stderr || removed.text || "Failed to remove git worktree" }) + return yield* new RemoveFailedError({ message: removed.stderr || removed.text || "Failed to remove git worktree" }) } } @@ -421,7 +435,7 @@ export const layer: Layer.Layer< if (branch) { const deleted = yield* git(["branch", "-D", branch], { cwd: ctx.worktree }) if (deleted.code !== 0) { - throw new RemoveFailedError({ + return yield* new RemoveFailedError({ message: deleted.stderr || deleted.text || "Failed to delete worktree branch", }) } @@ -436,7 +450,7 @@ export const layer: Layer.Layer< error: (r: GitResult) => Error, ) { const result = yield* git(args, opts) - if (result.code !== 0) throw error(result) + if (result.code !== 0) return yield* error(result) return result }) @@ -511,30 +525,30 @@ export const layer: Layer.Layer< const reset = Effect.fn("Worktree.reset")(function* (input: ResetInput) { const ctx = yield* InstanceState.context if (ctx.project.vcs !== "git") { - throw new NotGitError({ message: "Worktrees are only supported for git projects" }) + return yield* new NotGitError({ message: "Worktrees are only supported for git projects" }) } const directory = yield* canonical(input.directory) const primary = yield* canonical(ctx.worktree) if (directory === primary) { - throw new ResetFailedError({ message: "Cannot reset the primary workspace" }) + return yield* new ResetFailedError({ message: "Cannot reset the primary workspace" }) } const list = yield* git(["worktree", "list", "--porcelain"], { cwd: ctx.worktree }) if (list.code !== 0) { - throw new ResetFailedError({ message: list.stderr || list.text || "Failed to read git worktrees" }) + return yield* new ResetFailedError({ message: list.stderr || list.text || "Failed to read git worktrees" }) } const entry = yield* locateWorktree(parseWorktreeList(list.text), directory) if (!entry?.path) { - throw new ResetFailedError({ message: "Worktree not found" }) + return yield* new ResetFailedError({ message: "Worktree not found" }) } const worktreePath = entry.path const base = yield* gitSvc.defaultBranch(ctx.worktree) if (!base) { - throw new ResetFailedError({ message: "Default branch not found" }) + return yield* new ResetFailedError({ message: "Default branch not found" }) } const sep = base.ref.indexOf("/") @@ -556,7 +570,7 @@ export const layer: Layer.Layer< const cleanResult = yield* sweep(worktreePath) if (cleanResult.code !== 0) { - throw new ResetFailedError({ message: cleanResult.stderr || cleanResult.text || "Failed to clean worktree" }) + return yield* new ResetFailedError({ message: cleanResult.stderr || cleanResult.text || "Failed to clean worktree" }) } yield* gitExpect( @@ -579,11 +593,11 @@ export const layer: Layer.Layer< const status = yield* git(["-c", "core.fsmonitor=false", "status", "--porcelain=v1"], { cwd: worktreePath }) if (status.code !== 0) { - throw new ResetFailedError({ message: status.stderr || status.text || "Failed to read git status" }) + return yield* new ResetFailedError({ message: status.stderr || status.text || "Failed to read git status" }) } if (status.text.trim()) { - throw new ResetFailedError({ message: `Worktree reset left local changes:\n${status.text.trim()}` }) + return yield* new ResetFailedError({ message: `Worktree reset left local changes:\n${status.text.trim()}` }) } yield* runStartScripts(worktreePath, { projectID: ctx.project.id }).pipe( diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index 1de760014..308e2f957 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -135,13 +135,17 @@ describe("Worktree", () => { { git: true }, ) - it.instance("throws NotGitError for non-git directories", () => + it.instance("fails with NotGitError for non-git directories", () => Effect.gen(function* () { const svc = yield* Worktree.Service const exit = yield* Effect.exit(svc.makeWorktreeInfo()) expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Worktree.NotGitError) + if (Exit.isFailure(exit)) { + const error = Cause.squash(exit.cause) + expect(error).toBeInstanceOf(Worktree.NotGitError) + if (error instanceof Worktree.NotGitError) expect(error._tag).toBe("WorktreeNotGitError") + } }), ) @@ -286,14 +290,18 @@ describe("Worktree", () => { { git: true }, ) - it.instance("throws NotGitError for non-git directories", () => + it.instance("fails with NotGitError for non-git directories", () => Effect.gen(function* () { const test = yield* TestInstance const svc = yield* Worktree.Service const exit = yield* Effect.exit(svc.remove({ directory: path.join(test.directory, "fake") })) expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Worktree.NotGitError) + if (Exit.isFailure(exit)) { + const error = Cause.squash(exit.cause) + expect(error).toBeInstanceOf(Worktree.NotGitError) + if (error instanceof Worktree.NotGitError) expect(error._tag).toBe("WorktreeNotGitError") + } }), ) }) diff --git a/packages/opencode/test/server/httpapi-exercise/runner.ts b/packages/opencode/test/server/httpapi-exercise/runner.ts index 7ab5ccf99..b14647680 100644 --- a/packages/opencode/test/server/httpapi-exercise/runner.ts +++ b/packages/opencode/test/server/httpapi-exercise/runner.ts @@ -171,7 +171,7 @@ function withContext( messages: (sessionID) => run(modules.Session.Service.use((svc) => svc.messages({ sessionID }).pipe(Effect.orDie))), todos: (sessionID, todos) => run(modules.Todo.Service.use((svc) => svc.update({ sessionID, todos }))), - worktree: (input) => run(modules.Worktree.Service.use((svc) => svc.create(input))), + worktree: (input) => run(modules.Worktree.Service.use((svc) => svc.create(input).pipe(Effect.orDie))), worktreeRemove: (directory) => run(modules.Worktree.Service.use((svc) => svc.remove({ directory })).pipe(Effect.ignore)), llmText: (value) => Effect.suspend(() => llm().text(value)), diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index 11aed69cb..2613ee385 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -192,6 +192,23 @@ describe("experimental HttpApi", () => { }, ) + it.instance("returns declared worktree errors", () => + Effect.gen(function* () { + const tmp = yield* TestInstance + const response = yield* request(ExperimentalPaths.worktree, tmp.directory, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({}), + }) + + expect(response.status).toBe(400) + expect(yield* json(response)).toEqual({ + name: "WorktreeNotGitError", + data: { message: "Worktrees are only supported for git projects" }, + }) + }), + ) + it.instance( "serves Console org switch through the default server app", () => From 4498fc983dd0632feb4345a49940544bbb38866e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 09:36:51 +0000 Subject: [PATCH 243/378] chore: generate --- .../instance/httpapi/handlers/experimental.ts | 4 +- packages/opencode/src/worktree/index.ts | 12 +++-- packages/sdk/js/src/v2/gen/sdk.gen.ts | 3 +- packages/sdk/js/src/v2/gen/types.gen.ts | 35 ++++++++++--- packages/sdk/openapi.json | 51 ++++++++++++++++--- 5 files changed, 86 insertions(+), 19 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts index 89942ec4d..56a8de3ff 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts @@ -16,9 +16,7 @@ import { ConsoleSwitchPayload, SessionListQuery, ToolListQuery, WorktreeApiError function mapWorktreeError(self: Effect.Effect) { return self.pipe( - Effect.mapError( - (error) => new WorktreeApiError({ name: error._tag, data: { message: error.message } }), - ), + Effect.mapError((error) => new WorktreeApiError({ name: error._tag, data: { message: error.message } })), ) } diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 58651ddec..93e56605b 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -234,7 +234,9 @@ export const layer: Layer.Layer< { cwd: ctx.worktree }, ) if (created.code !== 0) { - return yield* new CreateFailedError({ message: created.stderr || created.text || "Failed to create git worktree" }) + return yield* new CreateFailedError({ + message: created.stderr || created.text || "Failed to create git worktree", + }) } yield* project.addSandbox(ctx.project.id, info.directory).pipe(Effect.catch(() => Effect.void)) @@ -425,7 +427,9 @@ export const layer: Layer.Layer< const stale = yield* locateWorktree(parseWorktreeList(next.text), directory) if (stale?.path) { - return yield* new RemoveFailedError({ message: removed.stderr || removed.text || "Failed to remove git worktree" }) + return yield* new RemoveFailedError({ + message: removed.stderr || removed.text || "Failed to remove git worktree", + }) } } @@ -570,7 +574,9 @@ export const layer: Layer.Layer< const cleanResult = yield* sweep(worktreePath) if (cleanResult.code !== 0) { - return yield* new ResetFailedError({ message: cleanResult.stderr || cleanResult.text || "Failed to clean worktree" }) + return yield* new ResetFailedError({ + message: cleanResult.stderr || cleanResult.text || "Failed to clean worktree", + }) } yield* gitExpect( diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index bf3201a5c..1b36c1513 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -214,6 +214,7 @@ import type { WorktreeCreateErrors, WorktreeCreateInput, WorktreeCreateResponses, + WorktreeListErrors, WorktreeListResponses, WorktreeRemoveErrors, WorktreeRemoveInput, @@ -1256,7 +1257,7 @@ export class Worktree extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/experimental/worktree", ...options, ...params, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 72b2a9f16..4350dff44 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1409,6 +1409,20 @@ export type ToolList = Array export type ToolIds = Array +export type WorktreeError = { + name: + | "WorktreeNotGitError" + | "WorktreeNameGenerationFailedError" + | "WorktreeCreateFailedError" + | "WorktreeStartCommandFailedError" + | "WorktreeRemoveFailedError" + | "WorktreeResetFailedError" + | "WorktreeListFailedError" + data: { + message: string + } +} + export type WorktreeCreateInput = { name?: string /** @@ -3843,9 +3857,9 @@ export type WorktreeRemoveData = { export type WorktreeRemoveErrors = { /** - * Bad request + * WorktreeError */ - 400: BadRequestError + 400: WorktreeError } export type WorktreeRemoveError = WorktreeRemoveErrors[keyof WorktreeRemoveErrors] @@ -3869,6 +3883,15 @@ export type WorktreeListData = { url: "/experimental/worktree" } +export type WorktreeListErrors = { + /** + * WorktreeError + */ + 400: WorktreeError +} + +export type WorktreeListError = WorktreeListErrors[keyof WorktreeListErrors] + export type WorktreeListResponses = { /** * List of worktree directories @@ -3890,9 +3913,9 @@ export type WorktreeCreateData = { export type WorktreeCreateErrors = { /** - * Bad request + * WorktreeError */ - 400: BadRequestError + 400: WorktreeError } export type WorktreeCreateError = WorktreeCreateErrors[keyof WorktreeCreateErrors] @@ -3918,9 +3941,9 @@ export type WorktreeResetData = { export type WorktreeResetErrors = { /** - * Bad request + * WorktreeError */ - 400: BadRequestError + 400: WorktreeError } export type WorktreeResetError = WorktreeResetErrors[keyof WorktreeResetErrors] diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index fbbe237ec..0a93e92ee 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1013,6 +1013,16 @@ } } } + }, + "400": { + "description": "WorktreeError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorktreeError" + } + } + } } }, "description": "List all sandbox worktrees for the current project.", @@ -1057,11 +1067,11 @@ } }, "400": { - "description": "Bad request", + "description": "WorktreeError", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "$ref": "#/components/schemas/WorktreeError" } } } @@ -1119,11 +1129,11 @@ } }, "400": { - "description": "Bad request", + "description": "WorktreeError", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "$ref": "#/components/schemas/WorktreeError" } } } @@ -1183,11 +1193,11 @@ } }, "400": { - "description": "Bad request", + "description": "WorktreeError", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "$ref": "#/components/schemas/WorktreeError" } } } @@ -12849,6 +12859,35 @@ "type": "string" } }, + "WorktreeError": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": [ + "WorktreeNotGitError", + "WorktreeNameGenerationFailedError", + "WorktreeCreateFailedError", + "WorktreeStartCommandFailedError", + "WorktreeRemoveFailedError", + "WorktreeResetFailedError", + "WorktreeListFailedError" + ] + }, + "data": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": ["message"], + "additionalProperties": false + } + }, + "required": ["name", "data"], + "additionalProperties": false + }, "WorktreeCreateInput": { "type": "object", "properties": { From 2e94f505a442d5be0d42c2307c3db12c49ee250e Mon Sep 17 00:00:00 2001 From: Victor Navarro Date: Wed, 13 May 2026 11:38:31 +0200 Subject: [PATCH 244/378] chore: add low tps model alerts (#27055) --- infra/monitoring.ts | 68 +++++++++++++++++++ .../app/src/routes/honeycomb/webhook.ts | 29 ++++++-- 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/infra/monitoring.ts b/infra/monitoring.ts index c08d39f26..edc70fecd 100644 --- a/infra/monitoring.ts +++ b/infra/monitoring.ts @@ -111,6 +111,34 @@ const providerHttpErrorsQuery = (product: "go" | "zen") => { }).json } +const modelLowTpsQuery = (product: "go" | "zen") => { + const filters = [ + { column: "model", op: "exists" }, + { column: "event_type", op: "=", value: "completions" }, + { column: "user_agent", op: "contains", value: "opencode" }, + { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" }, + { column: "status", op: ">=", value: "200" }, + { column: "status", op: "<", value: "400" }, + { column: "tps.output", op: "exists" }, + ] + + return honeycomb.getQuerySpecificationOutput({ + breakdowns: ["model"], + calculations: [ + { op: "COUNT", name: "TOTAL", filterCombination: "AND", filters }, + { + op: "P50", + name: "TPS", + column: "tps.output", + filterCombination: "AND", + filters, + }, + ], + formulas: [{ name: "LOW_TPS", expression: "IF(GTE($TOTAL, 100), $TPS, 999)" }], + timeRange: 900, + }).json +} + new honeycomb.Trigger("IncreasedModelHttpErrorsGo", { name: "Increased Model HTTP Errors [Go]", description, @@ -149,6 +177,46 @@ new honeycomb.Trigger("IncreasedModelHttpErrorsZen", { ], }) +new honeycomb.Trigger("LowModelTpsGo", { + disabled: true, + name: "Low Model TPS [Go]", + description, + queryJson: modelLowTpsQuery("go"), + alertType: "on_change", + frequency: 300, + thresholds: [{ op: "<", value: 20, exceededLimit: 1 }], + recipients: [ + { + id: webhookRecipient.id, + notificationDetails: [ + { + variables: [{ name: "type", value: "model_low_tps" }], + }, + ], + }, + ], +}) + +new honeycomb.Trigger("LowModelTpsZen", { + disabled: true, + name: "Low Model TPS [Zen]", + description, + queryJson: modelLowTpsQuery("zen"), + alertType: "on_change", + frequency: 300, + thresholds: [{ op: "<", value: 20, exceededLimit: 1 }], + recipients: [ + { + id: webhookRecipient.id, + notificationDetails: [ + { + variables: [{ name: "type", value: "model_low_tps" }], + }, + ], + }, + ], +}) + new honeycomb.Trigger("IncreasedProviderHttpErrorsGo", { name: "Increased Provider HTTP Errors [Go]", description, diff --git a/packages/console/app/src/routes/honeycomb/webhook.ts b/packages/console/app/src/routes/honeycomb/webhook.ts index 367a93aeb..ae76d86fe 100644 --- a/packages/console/app/src/routes/honeycomb/webhook.ts +++ b/packages/console/app/src/routes/honeycomb/webhook.ts @@ -12,13 +12,19 @@ const basePayload = z.object({ url: z.string(), }) -const groups = z.object({ group: z.object({ key: z.string(), value: z.string() }).array() }).array() +const groups = z + .object({ result: z.union([z.number(), z.string()]).nullish(), group: z.object({ key: z.string(), value: z.string() }).array() }) + .array() const honeycombWebhookPayload = z.discriminatedUnion("type", [ basePayload.extend({ type: z.literal("model_http_errors"), groups, }), + basePayload.extend({ + type: z.literal("model_low_tps"), + groups, + }), basePayload.extend({ type: z.literal("provider_http_errors"), groups, @@ -29,14 +35,25 @@ const honeycombWebhookPayload = z.discriminatedUnion("type", [ ]) const postDiscordMessage = async (payload: z.infer) => { - const group = - payload.type === "model_http_errors" ? "model" : payload.type === "provider_http_errors" ? "provider" : undefined - const names = payload.type === "custom" ? [] : payload.groups.flatMap((item) => item.group.map((g) => g.value)) + const names = + payload.type === "custom" + ? [] + : payload.groups.flatMap((item) => + item.group.map((g) => { + const result = item.result == null ? undefined : Number(item.result) + return `- ${g.value}${ + result !== undefined && Number.isFinite(result) + ? payload.type === "model_low_tps" + ? ` (${Math.round(result)} TPS)` + : ` (${Math.round(result * 100)}% errors)` + : "" + }` + }), + ) const content = [ `[**${payload.isTest ? "[TEST] " : ""}${payload.name ?? "Honeycomb alert"}**](${payload.url})`, - group && names.length > 0 ? `Affected ${group}s:` : undefined, - ...names.map((name) => `- ${name}`), + ...names, "", `<@&${DISCORD_ALERT_ROLE_ID}>`, ] From 374951bf60320d7f58dd8edfa4b204710627a964 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 09:39:54 +0000 Subject: [PATCH 245/378] chore: generate --- packages/console/app/src/routes/honeycomb/webhook.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/console/app/src/routes/honeycomb/webhook.ts b/packages/console/app/src/routes/honeycomb/webhook.ts index ae76d86fe..05be68353 100644 --- a/packages/console/app/src/routes/honeycomb/webhook.ts +++ b/packages/console/app/src/routes/honeycomb/webhook.ts @@ -13,7 +13,10 @@ const basePayload = z.object({ }) const groups = z - .object({ result: z.union([z.number(), z.string()]).nullish(), group: z.object({ key: z.string(), value: z.string() }).array() }) + .object({ + result: z.union([z.number(), z.string()]).nullish(), + group: z.object({ key: z.string(), value: z.string() }).array(), + }) .array() const honeycombWebhookPayload = z.discriminatedUnion("type", [ From 733bd3c74e9d7ad671bbfe235e74647179860ddd Mon Sep 17 00:00:00 2001 From: vimtor Date: Wed, 13 May 2026 12:12:03 +0200 Subject: [PATCH 246/378] chore: activate low tps alerts --- infra/monitoring.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/infra/monitoring.ts b/infra/monitoring.ts index edc70fecd..240e6c97e 100644 --- a/infra/monitoring.ts +++ b/infra/monitoring.ts @@ -135,7 +135,7 @@ const modelLowTpsQuery = (product: "go" | "zen") => { }, ], formulas: [{ name: "LOW_TPS", expression: "IF(GTE($TOTAL, 100), $TPS, 999)" }], - timeRange: 900, + timeRange: 1800, }).json } @@ -178,13 +178,12 @@ new honeycomb.Trigger("IncreasedModelHttpErrorsZen", { }) new honeycomb.Trigger("LowModelTpsGo", { - disabled: true, name: "Low Model TPS [Go]", description, queryJson: modelLowTpsQuery("go"), alertType: "on_change", - frequency: 300, - thresholds: [{ op: "<", value: 20, exceededLimit: 1 }], + frequency: 600, + thresholds: [{ op: "<=", value: 10, exceededLimit: 1 }], recipients: [ { id: webhookRecipient.id, @@ -198,13 +197,12 @@ new honeycomb.Trigger("LowModelTpsGo", { }) new honeycomb.Trigger("LowModelTpsZen", { - disabled: true, name: "Low Model TPS [Zen]", description, queryJson: modelLowTpsQuery("zen"), alertType: "on_change", - frequency: 300, - thresholds: [{ op: "<", value: 20, exceededLimit: 1 }], + frequency: 600, + thresholds: [{ op: "<=", value: 10, exceededLimit: 1 }], recipients: [ { id: webhookRecipient.id, From 809af5c59080858e8f61068616cb237c885e325e Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 15:47:13 +0530 Subject: [PATCH 247/378] fix(provider): type auth errors (#27301) --- packages/opencode/src/provider/auth.ts | 34 +++--- .../instance/httpapi/groups/provider.ts | 24 +++- .../instance/httpapi/handlers/provider.ts | 41 +++++-- .../instance/httpapi/middleware/error.ts | 1 - .../test/server/httpapi-provider.test.ts | 109 +++++++++++++++++- packages/sdk/js/src/v2/gen/types.gen.ts | 23 +++- 6 files changed, 200 insertions(+), 32 deletions(-) diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index b63e1eaf4..2e1b1e2af 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,7 +1,6 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" import { Auth } from "@/auth" import { InstanceState } from "@/effect/instance-state" -import { NamedError } from "@opencode-ai/core/util/error" import { optionalOmitUndefined } from "@opencode-ai/core/schema" import { Plugin } from "../plugin" import { ProviderID } from "./schema" @@ -64,23 +63,30 @@ export const CallbackInput = Schema.Struct({ }) export type CallbackInput = Schema.Schema.Type -export const OauthMissing = NamedError.create("ProviderAuthOauthMissing", { providerID: ProviderID }) +export class OauthMissing extends Schema.TaggedErrorClass()("ProviderAuthOauthMissing", { + providerID: ProviderID, +}) {} -export const OauthCodeMissing = NamedError.create("ProviderAuthOauthCodeMissing", { providerID: ProviderID }) +export class OauthCodeMissing extends Schema.TaggedErrorClass()("ProviderAuthOauthCodeMissing", { + providerID: ProviderID, +}) {} -export const OauthCallbackFailed = NamedError.create("ProviderAuthOauthCallbackFailed", {}) +export class OauthCallbackFailed extends Schema.TaggedErrorClass()( + "ProviderAuthOauthCallbackFailed", + {}, +) {} -export const ValidationFailed = NamedError.create("ProviderAuthValidationFailed", { +export class ValidationFailed extends Schema.TaggedErrorClass()("ProviderAuthValidationFailed", { field: Schema.String, message: Schema.String, -}) +}) {} export type Error = | Auth.AuthError - | InstanceType - | InstanceType - | InstanceType - | InstanceType + | OauthMissing + | OauthCodeMissing + | OauthCallbackFailed + | ValidationFailed type Hook = NonNullable @@ -166,7 +172,7 @@ export const layer: Layer.Layer = for (const prompt of method.prompts) { if (prompt.type === "text" && prompt.validate && input.inputs[prompt.key] !== undefined) { const error = prompt.validate(input.inputs[prompt.key]) - if (error) return yield* Effect.fail(new ValidationFailed({ field: prompt.key, message: error })) + if (error) return yield* new ValidationFailed({ field: prompt.key, message: error }) } } } @@ -183,15 +189,15 @@ export const layer: Layer.Layer = const callback = Effect.fn("ProviderAuth.callback")(function* (input: { providerID: ProviderID } & CallbackInput) { const pending = (yield* InstanceState.get(state)).pending const match = pending.get(input.providerID) - if (!match) return yield* Effect.fail(new OauthMissing({ providerID: input.providerID })) + if (!match) return yield* new OauthMissing({ providerID: input.providerID }) if (match.method === "code" && !input.code) { - return yield* Effect.fail(new OauthCodeMissing({ providerID: input.providerID })) + return yield* new OauthCodeMissing({ providerID: input.providerID }) } const result = yield* Effect.promise(() => match.method === "code" ? match.callback(input.code!) : match.callback(), ) - if (!result || result.type !== "success") return yield* Effect.fail(new OauthCallbackFailed({})) + if (!result || result.type !== "success") return yield* new OauthCallbackFailed({}) if ("key" in result) { yield* auth.set(input.providerID, { diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/provider.ts index 49792898d..b6eecff4c 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/provider.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/provider.ts @@ -10,6 +10,26 @@ import { described } from "./metadata" const root = "/provider" +const ProviderAuthErrorName = Schema.Union([ + Schema.Literal("BadRequest"), + Schema.Literal("ProviderAuthOauthMissing"), + Schema.Literal("ProviderAuthOauthCodeMissing"), + Schema.Literal("ProviderAuthOauthCallbackFailed"), + Schema.Literal("ProviderAuthValidationFailed"), +]) +export class ProviderAuthApiError extends Schema.ErrorClass("ProviderAuthError")( + { + name: ProviderAuthErrorName, + data: Schema.Struct({ + providerID: Schema.optional(ProviderID), + field: Schema.optional(Schema.String), + message: Schema.optional(Schema.String), + kind: Schema.optional(Schema.String), + }), + }, + { httpApiStatus: 400 }, +) {} + export const ProviderApi = HttpApi.make("provider") .add( HttpApiGroup.make("provider") @@ -39,7 +59,7 @@ export const ProviderApi = HttpApi.make("provider") query: WorkspaceRoutingQuery, payload: ProviderAuth.AuthorizeInput, success: described(Schema.UndefinedOr(ProviderAuth.Authorization), "Authorization URL and method"), - error: HttpApiError.BadRequest, + error: ProviderAuthApiError, }).annotateMerge( OpenApi.annotations({ identifier: "provider.oauth.authorize", @@ -52,7 +72,7 @@ export const ProviderApi = HttpApi.make("provider") query: WorkspaceRoutingQuery, payload: ProviderAuth.CallbackInput, success: described(Schema.Boolean, "OAuth callback processed successfully"), - error: HttpApiError.BadRequest, + error: ProviderAuthApiError, }).annotateMerge( OpenApi.annotations({ identifier: "provider.oauth.callback", diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts index b9d5b5af1..9da31582a 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts @@ -6,8 +6,29 @@ import { ProviderID } from "@/provider/schema" import { mapValues } from "remeda" import { Effect, Schema } from "effect" import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http" -import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi" +import { HttpApiBuilder } from "effect/unstable/httpapi" import { InstanceHttpApi } from "../api" +import { ProviderAuthApiError } from "../groups/provider" + +function mapProviderAuthError(self: Effect.Effect) { + return self.pipe( + Effect.mapError((error) => { + if (error instanceof ProviderAuth.OauthMissing) { + return new ProviderAuthApiError({ name: error._tag, data: { providerID: error.providerID } }) + } + if (error instanceof ProviderAuth.OauthCodeMissing) { + return new ProviderAuthApiError({ name: error._tag, data: { providerID: error.providerID } }) + } + if (error instanceof ProviderAuth.OauthCallbackFailed) { + return new ProviderAuthApiError({ name: error._tag, data: {} }) + } + if (error instanceof ProviderAuth.ValidationFailed) { + return new ProviderAuthApiError({ name: error._tag, data: { field: error.field, message: error.message } }) + } + return new ProviderAuthApiError({ name: "BadRequest", data: {} }) + }), + ) +} export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "provider", (handlers) => Effect.gen(function* () { @@ -44,13 +65,13 @@ export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "provider" params: { providerID: ProviderID } payload: ProviderAuth.AuthorizeInput }) { - return yield* svc - .authorize({ + return yield* mapProviderAuthError( + svc.authorize({ providerID: ctx.params.providerID, method: ctx.payload.method, inputs: ctx.payload.inputs, - }) - .pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({})))) + }), + ) }) const authorizeRaw = Effect.fn("ProviderHttpApi.authorizeRaw")(function* (ctx: { @@ -59,7 +80,7 @@ export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "provider" }) { const body = yield* Effect.orDie(ctx.request.text) const payload = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ProviderAuth.AuthorizeInput))(body).pipe( - Effect.mapError(() => new HttpApiError.BadRequest({})), + Effect.mapError(() => new ProviderAuthApiError({ name: "BadRequest", data: {} })), ) // Match legacy route behavior: when authorize() resolves without a // result (e.g. no further redirect), serialize as JSON `null` instead @@ -72,13 +93,13 @@ export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "provider" params: { providerID: ProviderID } payload: ProviderAuth.CallbackInput }) { - yield* svc - .callback({ + yield* mapProviderAuthError( + svc.callback({ providerID: ctx.params.providerID, method: ctx.payload.method, code: ctx.payload.code, - }) - .pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({})))) + }), + ) return true }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index acc39fb1e..bb75f6602 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -28,7 +28,6 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) HttpServerResponse.jsonUnsafe(error.toObject(), { status: iife(() => { if (error instanceof Provider.ModelNotFoundError) return 400 - if (error.name === "ProviderAuthValidationFailed") return 400 return 500 }), }), diff --git a/packages/opencode/test/server/httpapi-provider.test.ts b/packages/opencode/test/server/httpapi-provider.test.ts index cb47e5bbd..490b947fd 100644 --- a/packages/opencode/test/server/httpapi-provider.test.ts +++ b/packages/opencode/test/server/httpapi-provider.test.ts @@ -80,12 +80,33 @@ function requestAuthorize(input: { providerID: string method: number headers: HeadersInit + inputs?: Record }) { return Effect.promise(async () => { const response = await input.app.request(`/provider/${input.providerID}/oauth/authorize`, { method: "POST", headers: input.headers, - body: JSON.stringify({ method: input.method }), + body: JSON.stringify({ method: input.method, ...(input.inputs ? { inputs: input.inputs } : {}) }), + }) + return { + status: response.status, + body: await response.text(), + } + }) +} + +function requestCallback(input: { + app: ReturnType + providerID: string + method: number + headers: HeadersInit + code?: string +}) { + return Effect.promise(async () => { + const response = await input.app.request(`/provider/${input.providerID}/oauth/callback`, { + method: "POST", + headers: input.headers, + body: JSON.stringify({ method: input.method, ...(input.code ? { code: input.code } : {}) }), }) return { status: response.status, @@ -128,6 +149,47 @@ function writeProviderAuthPlugin(dir: string) { }) } +function writeProviderAuthValidationPlugin(dir: string) { + return Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + + yield* fs.writeWithDirs( + path.join(dir, ".opencode", "plugin", "provider-oauth-validation.ts"), + [ + "export default {", + ' id: "test.provider-oauth-validation",', + " server: async () => ({", + " auth: {", + ' provider: "test-oauth-validation",', + " methods: [", + " {", + ' type: "oauth",', + ' label: "OAuth",', + " prompts: [", + " {", + ' type: "text",', + ' key: "token",', + ' message: "Token",', + " validate: (value) => value === 'ok' ? undefined : 'Token must be ok',", + " },", + " ],", + " authorize: async () => ({", + ` url: "${oauthURL}",`, + ' method: "code",', + ` instructions: "${oauthInstructions}",`, + " callback: async () => ({ type: 'success', key: 'token' }),", + " }),", + " },", + " ],", + " },", + " }),", + "}", + "", + ].join("\n"), + ) + }) +} + function writeFunctionOptionsPlugin(dir: string) { return Effect.gen(function* () { const fs = yield* AppFileSystem.Service @@ -240,6 +302,51 @@ describe("provider HttpApi", () => { }) }), projectOptions, + 30000, + ) + + it.instance( + "returns declared provider auth validation errors", + Effect.gen(function* () { + const instance = yield* TestInstance + yield* writeProviderAuthValidationPlugin(instance.directory) + const response = yield* requestAuthorize({ + app: app(), + providerID: "test-oauth-validation", + method: 0, + inputs: { token: "nope" }, + headers: { "x-opencode-directory": instance.directory, "content-type": "application/json" }, + }) + + expect(response.status).toBe(400) + expect(JSON.parse(response.body)).toEqual({ + name: "ProviderAuthValidationFailed", + data: { field: "token", message: "Token must be ok" }, + }) + }), + projectOptions, + 30000, + ) + + it.instance( + "returns declared provider auth callback errors", + Effect.gen(function* () { + const instance = yield* TestInstance + const response = yield* requestCallback({ + app: app(), + providerID, + method: 0, + headers: { "x-opencode-directory": instance.directory, "content-type": "application/json" }, + }) + + expect(response.status).toBe(400) + expect(JSON.parse(response.body)).toEqual({ + name: "ProviderAuthOauthMissing", + data: { providerID }, + }) + }), + projectOptions, + 30000, ) it.instance( diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 4350dff44..a1591aa2c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1721,6 +1721,21 @@ export type ProviderAuthAuthorization = { instructions: string } +export type ProviderAuthError1 = { + name: + | "BadRequest" + | "ProviderAuthOauthMissing" + | "ProviderAuthOauthCodeMissing" + | "ProviderAuthOauthCallbackFailed" + | "ProviderAuthValidationFailed" + data: { + providerID?: string + field?: string + message?: string + kind?: string + } +} + export type TextPartInput = { id?: string type: "text" @@ -5155,9 +5170,9 @@ export type ProviderOauthAuthorizeData = { export type ProviderOauthAuthorizeErrors = { /** - * Bad request + * ProviderAuthError */ - 400: BadRequestError + 400: ProviderAuthError1 } export type ProviderOauthAuthorizeError = ProviderOauthAuthorizeErrors[keyof ProviderOauthAuthorizeErrors] @@ -5191,9 +5206,9 @@ export type ProviderOauthCallbackData = { export type ProviderOauthCallbackErrors = { /** - * Bad request + * ProviderAuthError */ - 400: BadRequestError + 400: ProviderAuthError1 } export type ProviderOauthCallbackError = ProviderOauthCallbackErrors[keyof ProviderOauthCallbackErrors] From d488e3fd2a6fabcc65a0df758246bf17a409f288 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 10:18:40 +0000 Subject: [PATCH 248/378] chore: generate --- packages/opencode/src/provider/auth.ts | 7 +---- packages/sdk/openapi.json | 43 +++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 2e1b1e2af..6f857ce52 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -81,12 +81,7 @@ export class ValidationFailed extends Schema.TaggedErrorClass( message: Schema.String, }) {} -export type Error = - | Auth.AuthError - | OauthMissing - | OauthCodeMissing - | OauthCallbackFailed - | ValidationFailed +export type Error = Auth.AuthError | OauthMissing | OauthCodeMissing | OauthCallbackFailed | ValidationFailed type Hook = NonNullable diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 0a93e92ee..fc7e4c278 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -4217,11 +4217,11 @@ } }, "400": { - "description": "Bad request", + "description": "ProviderAuthError", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "$ref": "#/components/schemas/ProviderAuthError1" } } } @@ -4303,11 +4303,11 @@ } }, "400": { - "description": "Bad request", + "description": "ProviderAuthError", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "$ref": "#/components/schemas/ProviderAuthError1" } } } @@ -13785,6 +13785,41 @@ "required": ["url", "method", "instructions"], "additionalProperties": false }, + "ProviderAuthError1": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": [ + "BadRequest", + "ProviderAuthOauthMissing", + "ProviderAuthOauthCodeMissing", + "ProviderAuthOauthCallbackFailed", + "ProviderAuthValidationFailed" + ] + }, + "data": { + "type": "object", + "properties": { + "providerID": { + "type": "string" + }, + "field": { + "type": "string" + }, + "message": { + "type": "string" + }, + "kind": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "required": ["name", "data"], + "additionalProperties": false + }, "TextPartInput": { "type": "object", "properties": { From 5b2b30060228eaff29c4b3d576dccaf02051deeb Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 16:48:18 +0530 Subject: [PATCH 249/378] fix(session): tighten http error contracts (#27308) --- .../routes/instance/httpapi/groups/session.ts | 28 +++++------ .../instance/httpapi/handlers/session.ts | 39 ++++++++++----- .../test/server/httpapi-session.test.ts | 49 +++++++++++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 30 +++++------- 4 files changed, 104 insertions(+), 42 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts index 2053aba3b..b8c8a142b 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts @@ -141,7 +141,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID }, query: WorkspaceRoutingQuery, success: described(Schema.Array(Session.Info), "List of children"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.children", @@ -153,7 +153,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID }, query: WorkspaceRoutingQuery, success: described(Schema.Array(Todo.Info), "Todo list"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.todo", @@ -250,7 +250,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID }, query: WorkspaceRoutingQuery, success: described(Schema.Boolean, "Aborted session"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: HttpApiError.BadRequest, }).annotateMerge( OpenApi.annotations({ identifier: "session.abort", @@ -263,7 +263,7 @@ export const SessionApi = HttpApi.make("session") query: WorkspaceRoutingQuery, payload: InitPayload, success: described(Schema.Boolean, "200"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.init", @@ -314,7 +314,7 @@ export const SessionApi = HttpApi.make("session") query: WorkspaceRoutingQuery, payload: PromptPayload, success: described(MessageV2.WithParts, "Created message"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.prompt", @@ -327,7 +327,7 @@ export const SessionApi = HttpApi.make("session") query: WorkspaceRoutingQuery, payload: PromptPayload, success: described(HttpApiSchema.NoContent, "Prompt accepted"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.prompt_async", @@ -341,7 +341,7 @@ export const SessionApi = HttpApi.make("session") query: WorkspaceRoutingQuery, payload: CommandPayload, success: described(MessageV2.WithParts, "Created message"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.command", @@ -354,7 +354,7 @@ export const SessionApi = HttpApi.make("session") query: WorkspaceRoutingQuery, payload: ShellPayload, success: described(MessageV2.WithParts, "Created message"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.shell", @@ -367,7 +367,7 @@ export const SessionApi = HttpApi.make("session") query: WorkspaceRoutingQuery, payload: RevertPayload, success: described(Session.Info, "Updated session"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.revert", @@ -380,7 +380,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID }, query: WorkspaceRoutingQuery, success: described(Session.Info, "Updated session"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.unrevert", @@ -393,7 +393,7 @@ export const SessionApi = HttpApi.make("session") query: WorkspaceRoutingQuery, payload: PermissionResponsePayload, success: described(Schema.Boolean, "Permission processed successfully"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "permission.respond", @@ -406,7 +406,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID, messageID: MessageID }, query: WorkspaceRoutingQuery, success: described(Schema.Boolean, "Successfully deleted message"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "session.deleteMessage", @@ -419,7 +419,7 @@ export const SessionApi = HttpApi.make("session") params: { sessionID: SessionID, messageID: MessageID, partID: PartID }, query: WorkspaceRoutingQuery, success: described(Schema.Boolean, "Successfully deleted part"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "part.delete", @@ -431,7 +431,7 @@ export const SessionApi = HttpApi.make("session") query: WorkspaceRoutingQuery, payload: MessageV2.Part, success: described(MessageV2.Part, "Successfully updated part"), - error: [HttpApiError.BadRequest, HttpApiError.NotFound], + error: [HttpApiError.BadRequest, ApiNotFoundError], }).annotateMerge( OpenApi.annotations({ identifier: "part.update", diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 40444385f..b12be2cfc 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -68,15 +68,21 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", return Object.fromEntries(yield* statusSvc.list()) }) + const requireSession = Effect.fn("SessionHttpApi.requireSession")(function* (sessionID: SessionID) { + return yield* SessionError.mapStorageNotFound(session.get(sessionID)) + }) + const get = Effect.fn("SessionHttpApi.get")(function* (ctx: { params: { sessionID: SessionID } }) { - return yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID)) + return yield* requireSession(ctx.params.sessionID) }) const children = Effect.fn("SessionHttpApi.children")(function* (ctx: { params: { sessionID: SessionID } }) { + yield* requireSession(ctx.params.sessionID) return yield* session.children(ctx.params.sessionID) }) const todo = Effect.fn("SessionHttpApi.todo")(function* (ctx: { params: { sessionID: SessionID } }) { + yield* requireSession(ctx.params.sessionID) return yield* todoSvc.get(ctx.params.sessionID) }) @@ -99,7 +105,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", catch: () => new HttpApiError.BadRequest({}), }) } - yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID)) + yield* requireSession(ctx.params.sessionID) if (ctx.query.limit === undefined || ctx.query.limit === 0) { return yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID })) } @@ -165,7 +171,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID } payload: typeof UpdatePayload.Type }) { - const current = yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID)) + const current = yield* requireSession(ctx.params.sessionID) if (ctx.payload.title !== undefined) { yield* session.setTitle({ sessionID: ctx.params.sessionID, title: ctx.payload.title }) } @@ -178,7 +184,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", if (ctx.payload.time?.archived !== undefined) { yield* session.setArchived({ sessionID: ctx.params.sessionID, time: ctx.payload.time.archived }) } - return yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID)) + return yield* requireSession(ctx.params.sessionID) }) const fork = Effect.fn("SessionHttpApi.fork")(function* (ctx: { @@ -216,6 +222,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID } payload: typeof InitPayload.Type }) { + yield* requireSession(ctx.params.sessionID) yield* promptSvc .command({ sessionID: ctx.params.sessionID, @@ -234,22 +241,24 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", // ErrorMiddleware → NamedError.Unknown 500) instead of blanket-mapping // every failure to a 400 BadRequest. const share = Effect.fn("SessionHttpApi.share")(function* (ctx: { params: { sessionID: SessionID } }) { + yield* requireSession(ctx.params.sessionID) yield* shareSvc.share(ctx.params.sessionID).pipe(Effect.mapError(() => new HttpApiError.InternalServerError({}))) - return yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID)) + return yield* requireSession(ctx.params.sessionID) }) const unshare = Effect.fn("SessionHttpApi.unshare")(function* (ctx: { params: { sessionID: SessionID } }) { + yield* requireSession(ctx.params.sessionID) yield* shareSvc .unshare(ctx.params.sessionID) .pipe(Effect.mapError(() => new HttpApiError.InternalServerError({}))) - return yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID)) + return yield* requireSession(ctx.params.sessionID) }) const summarize = Effect.fn("SessionHttpApi.summarize")(function* (ctx: { params: { sessionID: SessionID } payload: typeof SummarizePayload.Type }) { - yield* revertSvc.cleanup(yield* SessionError.mapStorageNotFound(session.get(ctx.params.sessionID))) + yield* revertSvc.cleanup(yield* requireSession(ctx.params.sessionID)) const messages = yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID })) const defaultAgent = yield* agentSvc.defaultAgent() const currentAgent = messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent @@ -271,6 +280,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID } payload: typeof PromptPayload.Type }) { + yield* requireSession(ctx.params.sessionID) const message = yield* promptSvc .prompt({ ...ctx.payload, @@ -286,6 +296,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID } payload: typeof PromptPayload.Type }) { + yield* requireSession(ctx.params.sessionID) yield* promptSvc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID }).pipe( Effect.catchCause((cause) => Effect.gen(function* () { @@ -307,6 +318,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID } payload: typeof CommandPayload.Type }) { + yield* requireSession(ctx.params.sessionID) return yield* promptSvc .command({ ...ctx.payload, sessionID: ctx.params.sessionID }) .pipe(Effect.mapError(() => new HttpApiError.BadRequest({}))) @@ -316,6 +328,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID } payload: typeof ShellPayload.Type }) { + yield* requireSession(ctx.params.sessionID) return yield* promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID }) }) @@ -323,17 +336,20 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID } payload: typeof RevertPayload.Type }) { + yield* requireSession(ctx.params.sessionID) return yield* revertSvc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload }) }) const unrevert = Effect.fn("SessionHttpApi.unrevert")(function* (ctx: { params: { sessionID: SessionID } }) { + yield* requireSession(ctx.params.sessionID) return yield* revertSvc.unrevert({ sessionID: ctx.params.sessionID }) }) const permissionRespond = Effect.fn("SessionHttpApi.permissionRespond")(function* (ctx: { - params: { permissionID: PermissionID } + params: { sessionID: SessionID; permissionID: PermissionID } payload: typeof PermissionResponsePayload.Type }) { + yield* requireSession(ctx.params.sessionID) yield* permissionSvc.reply({ requestID: ctx.params.permissionID, reply: ctx.payload.response }) return true }) @@ -341,6 +357,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", const deleteMessage = Effect.fn("SessionHttpApi.deleteMessage")(function* (ctx: { params: { sessionID: SessionID; messageID: MessageID } }) { + yield* requireSession(ctx.params.sessionID) yield* runState.assertNotBusy(ctx.params.sessionID) yield* session.removeMessage(ctx.params) return true @@ -349,6 +366,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", const deletePart = Effect.fn("SessionHttpApi.deletePart")(function* (ctx: { params: { sessionID: SessionID; messageID: MessageID; partID: PartID } }) { + yield* requireSession(ctx.params.sessionID) yield* session.removePart(ctx.params) return true }) @@ -357,15 +375,14 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID; messageID: MessageID; partID: PartID } payload: typeof MessageV2.Part.Type }) { + yield* requireSession(ctx.params.sessionID) const payload = ctx.payload as MessageV2.Part if ( payload.id !== ctx.params.partID || payload.messageID !== ctx.params.messageID || payload.sessionID !== ctx.params.sessionID ) { - throw new Error( - `Part mismatch: body.id='${payload.id}' vs partID='${ctx.params.partID}', body.messageID='${payload.messageID}' vs messageID='${ctx.params.messageID}', body.sessionID='${payload.sessionID}' vs sessionID='${ctx.params.sessionID}'`, - ) + return yield* new HttpApiError.BadRequest({}) } return yield* session.updatePart(payload) }) diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 8cffb6e82..8b686739d 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -212,6 +212,14 @@ describe("session HttpApi", () => { expect(get.status).toBe(404) expect(yield* responseJson(get)).toEqual(missingSessionBody) + const children = yield* request(pathFor(SessionPaths.children, { sessionID: missingSession }), { headers }) + expect(children.status).toBe(404) + expect(yield* responseJson(children)).toEqual(missingSessionBody) + + const todo = yield* request(pathFor(SessionPaths.todo, { sessionID: missingSession }), { headers }) + expect(todo.status).toBe(404) + expect(yield* responseJson(todo)).toEqual(missingSessionBody) + const messages = yield* request(pathFor(SessionPaths.messages, { sessionID: missingSession }), { headers }) expect(messages.status).toBe(404) expect(yield* responseJson(messages)).toEqual(missingSessionBody) @@ -223,6 +231,21 @@ describe("session HttpApi", () => { expect(remove.status).toBe(404) expect(yield* responseJson(remove)).toEqual(missingSessionBody) + const prompt = yield* request(pathFor(SessionPaths.prompt, { sessionID: missingSession }), { + headers: { ...headers, "content-type": "application/json" }, + method: "POST", + body: JSON.stringify({ agent: "build", noReply: true, parts: [{ type: "text", text: "hello" }] }), + }) + expect(prompt.status).toBe(404) + expect(yield* responseJson(prompt)).toEqual(missingSessionBody) + + const abort = yield* request(pathFor(SessionPaths.abort, { sessionID: missingSession }), { + headers, + method: "POST", + }) + expect(abort.status).toBe(200) + expect(yield* responseJson(abort)).toBe(true) + const session = yield* createSession({ title: "missing message" }) const missingMessage = MessageID.ascending() const message = yield* request( @@ -530,6 +553,32 @@ describe("session HttpApi", () => { { git: true, config: { formatter: false, lsp: false } }, ) + it.instance( + "rejects part updates whose path and body ids disagree", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const headers = { "x-opencode-directory": test.directory, "content-type": "application/json" } + const session = yield* createSession({ title: "part mismatch" }) + const message = yield* createTextMessage(session.id, "first") + const response = yield* request( + pathFor(SessionPaths.updatePart, { + sessionID: session.id, + messageID: message.info.id, + partID: message.part.id, + }), + { + method: "PATCH", + headers, + body: JSON.stringify({ ...message.part, id: PartID.ascending() }), + }, + ) + + expect(response.status).toBe(400) + }), + { git: true, config: { formatter: false, lsp: false } }, + ) + it.instance( "serves remaining non-LLM session mutation routes", () => diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a1591aa2c..35fb6bf81 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -5442,7 +5442,7 @@ export type SessionChildrenErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -5476,7 +5476,7 @@ export type SessionTodoErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -5586,7 +5586,7 @@ export type SessionPromptErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -5624,7 +5624,7 @@ export type SessionDeleteMessageErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -5731,10 +5731,6 @@ export type SessionAbortErrors = { * Bad request */ 400: BadRequestError - /** - * Not found - */ - 404: NotFoundError } export type SessionAbortError = SessionAbortErrors[keyof SessionAbortErrors] @@ -5770,7 +5766,7 @@ export type SessionInitErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -5925,7 +5921,7 @@ export type SessionPromptAsyncErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -5974,7 +5970,7 @@ export type SessionCommandErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -6019,7 +6015,7 @@ export type SessionShellErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -6059,7 +6055,7 @@ export type SessionRevertErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -6093,7 +6089,7 @@ export type SessionUnrevertErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -6130,7 +6126,7 @@ export type PermissionRespondErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -6166,7 +6162,7 @@ export type PartDeleteErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } @@ -6202,7 +6198,7 @@ export type PartUpdateErrors = { */ 400: BadRequestError /** - * Not found + * NotFoundError */ 404: NotFoundError } From 5975547c845432a90bec743032dee148a43be9f3 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 11:19:45 +0000 Subject: [PATCH 250/378] chore: generate --- packages/sdk/openapi.json | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index fc7e4c278..97890a5dc 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -4903,7 +4903,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -4980,7 +4980,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -5237,7 +5237,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -5484,7 +5484,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -5645,16 +5645,6 @@ } } } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } } }, "description": "Abort an active session and stop any ongoing AI processing or command execution.", @@ -5721,7 +5711,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -6050,7 +6040,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -6205,7 +6195,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -6354,7 +6344,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -6463,7 +6453,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -6557,7 +6547,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -6640,7 +6630,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -6750,7 +6740,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { @@ -6838,7 +6828,7 @@ } }, "404": { - "description": "Not found", + "description": "NotFoundError", "content": { "application/json": { "schema": { From 4d205027caa2a965a7bef85762f0b8a1526b344a Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 17:07:53 +0530 Subject: [PATCH 251/378] refactor(flags): route scout through runtime flags (#27318) --- packages/opencode/src/agent/agent.ts | 6 +- packages/opencode/src/reference/reference.ts | 12 +- packages/opencode/test/agent/agent.test.ts | 122 +++++---- .../agent/plugin-agent-regression.test.ts | 1 + .../opencode/test/reference/reference.test.ts | 239 +++++++++--------- packages/opencode/test/tool/read.test.ts | 103 ++++---- 6 files changed, 238 insertions(+), 245 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 423a51318..1e4d7e156 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -15,12 +15,12 @@ import PROMPT_TITLE from "./prompt/title.txt" import { Permission } from "@/permission" import { mergeDeep, pipe, sortBy, values } from "remeda" import { Global } from "@opencode-ai/core/global" -import { Flag } from "@opencode-ai/core/flag/flag" import path from "path" import { Plugin } from "@/plugin" import { Skill } from "../skill" import { Effect, Context, Layer, Schema } from "effect" import { InstanceState } from "@/effect/instance-state" +import { RuntimeFlags } from "@/effect/runtime-flags" import * as Option from "effect/Option" import * as OtelTracer from "@effect/opentelemetry/Tracer" import { type DeepMutable } from "@opencode-ai/core/schema" @@ -81,6 +81,7 @@ export const layer = Layer.effect( const plugin = yield* Plugin.Service const skill = yield* Skill.Service const provider = yield* Provider.Service + const flags = yield* RuntimeFlags.Service const state = yield* InstanceState.make( Effect.fn("Agent.state")(function* (ctx) { @@ -195,7 +196,7 @@ export const layer = Layer.effect( mode: "subagent", native: true, }, - ...(Flag.OPENCODE_EXPERIMENTAL_SCOUT + ...(flags.experimentalScout ? { scout: { name: "scout", @@ -453,6 +454,7 @@ export const defaultLayer = layer.pipe( Layer.provide(Auth.defaultLayer), Layer.provide(Config.defaultLayer), Layer.provide(Skill.defaultLayer), + Layer.provide(RuntimeFlags.defaultLayer), ) export * as Agent from "./agent" diff --git a/packages/opencode/src/reference/reference.ts b/packages/opencode/src/reference/reference.ts index 748c3b238..3109c3749 100644 --- a/packages/opencode/src/reference/reference.ts +++ b/packages/opencode/src/reference/reference.ts @@ -1,10 +1,10 @@ import path from "path" import { Effect, Context, Layer, Scope } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" import { Config } from "@/config/config" import { InstanceState } from "@/effect/instance-state" +import { RuntimeFlags } from "@/effect/runtime-flags" import { Git } from "@/git" import { parseRepositoryReference, repositoryCachePath, type Reference as RepositoryReference } from "@/util/repository" import { RepositoryCache } from "./repository-cache" @@ -143,6 +143,7 @@ export const layer = Layer.effect( const fs = yield* AppFileSystem.Service const git = yield* Git.Service const scope = yield* Scope.Scope + const flags = yield* RuntimeFlags.Service const state = yield* InstanceState.make( Effect.fn("Reference.state")(function* (ctx) { @@ -181,7 +182,7 @@ export const layer = Layer.effect( ) const materializeAll = yield* Effect.cached( - Flag.OPENCODE_EXPERIMENTAL_SCOUT + flags.experimentalScout ? Effect.gen(function* () { yield* Effect.forEach( materializeByPath, @@ -200,7 +201,7 @@ export const layer = Layer.effect( return Service.of({ init: Effect.fn("Reference.init")(function* () { - if (!Flag.OPENCODE_EXPERIMENTAL_SCOUT) return + if (!flags.experimentalScout) return yield* InstanceState.useEffect(state, (s) => s.materializeAll).pipe(Effect.forkIn(scope), Effect.asVoid) }), list: Effect.fn("Reference.list")(function* () { @@ -210,7 +211,7 @@ export const layer = Layer.effect( return yield* InstanceState.use(state, (s) => s.references.find((reference) => reference.name === name)) }), ensure: Effect.fn("Reference.ensure")(function* (target?: string) { - if (!Flag.OPENCODE_EXPERIMENTAL_SCOUT) return + if (!flags.experimentalScout) return const full = normalizedTarget(target) if (!full) return yield* InstanceState.useEffect(state, (s) => s.materializeAll) return yield* InstanceState.useEffect( @@ -219,7 +220,7 @@ export const layer = Layer.effect( ) }), contains: Effect.fn("Reference.contains")(function* (target?: string) { - if (!Flag.OPENCODE_EXPERIMENTAL_SCOUT) return false + if (!flags.experimentalScout) return false const full = normalizedTarget(target) if (!full) return false return yield* InstanceState.use(state, (s) => @@ -234,6 +235,7 @@ export const defaultLayer = layer.pipe( Layer.provide(Config.defaultLayer), Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Git.defaultLayer), + Layer.provide(RuntimeFlags.defaultLayer), ) export * as Reference from "./reference" diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index a781a855c..e0defc138 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -1,15 +1,31 @@ import { afterEach, expect } from "bun:test" -import { Cause, Effect, Exit } from "effect" +import { Cause, Effect, Exit, Layer } from "effect" import path from "path" import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { Agent } from "../../src/agent/agent" +import { Auth } from "../../src/auth" +import { Config } from "../../src/config/config" +import { RuntimeFlags } from "../../src/effect/runtime-flags" import { Global } from "@opencode-ai/core/global" -import { Flag } from "@opencode-ai/core/flag/flag" import { Permission } from "../../src/permission" +import { Plugin } from "../../src/plugin" +import { Provider } from "../../src/provider/provider" +import { Skill } from "../../src/skill" import { Truncate } from "../../src/tool/truncate" -const it = testEffect(Agent.defaultLayer) +const agentLayer = (flags: Partial = {}) => + Agent.layer.pipe( + Layer.provide(Plugin.defaultLayer), + Layer.provide(Provider.defaultLayer), + Layer.provide(Auth.defaultLayer), + Layer.provide(Config.defaultLayer), + Layer.provide(Skill.defaultLayer), + Layer.provide(RuntimeFlags.layer(flags)), + ) + +const it = testEffect(agentLayer()) +const scout = testEffect(agentLayer({ experimentalScout: true })) // Helper to evaluate permission for a tool with wildcard pattern function evalPerm(agent: Agent.Info | undefined, permission: string): Permission.Action | undefined { @@ -21,21 +37,6 @@ function load
(fn: (svc: Agent.Interface) => Effect.Effect) { return Agent.Service.use(fn) } -function withExperimentalScout(enabled: boolean, self: Effect.Effect) { - return Effect.acquireUseRelease( - Effect.sync(() => { - const original = Flag.OPENCODE_EXPERIMENTAL_SCOUT - Flag.OPENCODE_EXPERIMENTAL_SCOUT = enabled - return original - }), - () => self, - (original) => - Effect.sync(() => { - Flag.OPENCODE_EXPERIMENTAL_SCOUT = original - }), - ) -} - const expectDefaultAgentError = Effect.fn("AgentTest.expectDefaultAgentError")(function* (message: string) { const exit = yield* load((svc) => svc.defaultAgent()).pipe(Effect.exit) expect(Exit.isFailure(exit)).toBe(true) @@ -47,21 +48,18 @@ afterEach(async () => { }) it.instance("returns default native agents when no config", () => - withExperimentalScout( - false, - Effect.gen(function* () { - const agents = yield* load((svc) => svc.list()) - const names = agents.map((a) => a.name) - expect(names).toContain("build") - expect(names).toContain("plan") - expect(names).toContain("general") - expect(names).toContain("explore") - expect(names).not.toContain("scout") - expect(names).toContain("compaction") - expect(names).toContain("title") - expect(names).toContain("summary") - }), - ), + Effect.gen(function* () { + const agents = yield* load((svc) => svc.list()) + const names = agents.map((a) => a.name) + expect(names).toContain("build") + expect(names).toContain("plan") + expect(names).toContain("general") + expect(names).toContain("explore") + expect(names).not.toContain("scout") + expect(names).toContain("compaction") + expect(names).toContain("title") + expect(names).toContain("summary") + }), ) it.instance("build agent has correct default properties", () => @@ -111,42 +109,36 @@ it.instance("explore agent asks for external directories and allows whitelisted }), ) -it.instance("scout agent allows repo cloning and repo cache reads", () => - withExperimentalScout( - true, - Effect.gen(function* () { - const scout = yield* load((svc) => svc.get("scout")) - expect(scout).toBeDefined() - expect(scout?.mode).toBe("subagent") - expect(evalPerm(scout, "repo_clone")).toBe("allow") - expect(evalPerm(scout, "repo_overview")).toBe("allow") - expect(evalPerm(scout, "edit")).toBe("deny") - expect( - Permission.evaluate( - "external_directory", - path.join(Global.Path.repos, "github.com", "owner", "repo", "README.md"), - scout!.permission, - ).action, - ).toBe("allow") - }), - ), +scout.instance("scout agent allows repo cloning and repo cache reads", () => + Effect.gen(function* () { + const scout = yield* load((svc) => svc.get("scout")) + expect(scout).toBeDefined() + expect(scout?.mode).toBe("subagent") + expect(evalPerm(scout, "repo_clone")).toBe("allow") + expect(evalPerm(scout, "repo_overview")).toBe("allow") + expect(evalPerm(scout, "edit")).toBe("deny") + expect( + Permission.evaluate( + "external_directory", + path.join(Global.Path.repos, "github.com", "owner", "repo", "README.md"), + scout!.permission, + ).action, + ).toBe("allow") + }), ) -it.instance( +scout.instance( "reference config does not create subagents", () => - withExperimentalScout( - true, - Effect.gen(function* () { - const agents = yield* load((svc) => svc.list()) - const names = agents.map((agent) => agent.name) - expect(names).toContain("scout") - expect(names).not.toContain("effect") - expect(names).not.toContain("effectFull") - expect(names).not.toContain("localdocs") - expect(names).not.toContain("localdocsFull") - }), - ), + Effect.gen(function* () { + const agents = yield* load((svc) => svc.list()) + const names = agents.map((agent) => agent.name) + expect(names).toContain("scout") + expect(names).not.toContain("effect") + expect(names).not.toContain("effectFull") + expect(names).not.toContain("localdocs") + expect(names).not.toContain("localdocsFull") + }), { config: { reference: { diff --git a/packages/opencode/test/agent/plugin-agent-regression.test.ts b/packages/opencode/test/agent/plugin-agent-regression.test.ts index 60d59ee90..c437281cc 100644 --- a/packages/opencode/test/agent/plugin-agent-regression.test.ts +++ b/packages/opencode/test/agent/plugin-agent-regression.test.ts @@ -41,6 +41,7 @@ const agentLayer = Agent.layer.pipe( Layer.provide(SkillTest.empty), Layer.provide(provider.layer), Layer.provide(pluginLayer), + Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })), ) const it = testEffect(Layer.mergeAll(agentLayer, pluginLayer)) diff --git a/packages/opencode/test/reference/reference.test.ts b/packages/opencode/test/reference/reference.test.ts index 4717c61d2..f4a73dbf9 100644 --- a/packages/opencode/test/reference/reference.test.ts +++ b/packages/opencode/test/reference/reference.test.ts @@ -3,8 +3,9 @@ import path from "path" import { Effect, Layer } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" +import { Config } from "../../src/config/config" +import { RuntimeFlags } from "../../src/effect/runtime-flags" import { Git } from "../../src/git" import { Reference } from "../../src/reference/reference" import { disposeAllInstances, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture" @@ -14,23 +15,25 @@ afterEach(async () => { await disposeAllInstances() }) +const referenceLayer = (flags: Partial = {}) => + Reference.layer.pipe( + Layer.provide(Config.defaultLayer), + Layer.provide(AppFileSystem.defaultLayer), + Layer.provide(Git.defaultLayer), + Layer.provide(RuntimeFlags.layer(flags)), + ) + const it = testEffect( - Layer.mergeAll(AppFileSystem.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer, Reference.defaultLayer), + Layer.mergeAll(AppFileSystem.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer, referenceLayer()), +) +const scout = testEffect( + Layer.mergeAll( + AppFileSystem.defaultLayer, + CrossSpawnSpawner.defaultLayer, + Git.defaultLayer, + referenceLayer({ experimentalScout: true }), + ), ) - -const experimentalScout = (self: Effect.Effect) => - Effect.acquireUseRelease( - Effect.sync(() => { - const previous = Flag.OPENCODE_EXPERIMENTAL_SCOUT - Flag.OPENCODE_EXPERIMENTAL_SCOUT = true - return previous - }), - () => self, - (previous) => - Effect.sync(() => { - Flag.OPENCODE_EXPERIMENTAL_SCOUT = previous - }), - ) const githubBase = (url: string, self: Effect.Effect) => Effect.acquireUseRelease( @@ -127,118 +130,114 @@ describe("reference", () => { }), ) - it.live("materializes configured git references during init", () => - experimentalScout( - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-test", "repo") - yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore) - yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore)) - - const source = yield* tmpdirScoped({ git: true }) - const remoteRoot = yield* tmpdirScoped() - const remoteDir = path.join(remoteRoot, "opencode-reference-test") - const remoteRepo = path.join(remoteDir, "repo.git") - - yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "configured\n")) - yield* git(source, ["add", "."]) - yield* git(source, ["commit", "-m", "add readme"]) - yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie) - yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo]) - - const reference = yield* Reference.Service - yield* githubBase( - `file://${remoteRoot}/`, - Effect.gen(function* () { - yield* reference.init() - yield* waitForContent(fs, path.join(cache, "README.md"), "configured\n") - }), - ) - - expect(yield* fs.existsSafe(path.join(cache, ".git"))).toBe(true) - expect(yield* fs.readFileString(path.join(cache, "README.md"))).toBe("configured\n") - - const resolved = yield* reference.get("docs") - expect(resolved?.kind).toBe("git") - if (resolved?.kind === "git") expect(resolved.path).toBe(cache) - }), - { - config: { - reference: { - docs: "opencode-reference-test/repo", - }, + scout.live("materializes configured git references during init", () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-test", "repo") + yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore) + yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore)) + + const source = yield* tmpdirScoped({ git: true }) + const remoteRoot = yield* tmpdirScoped() + const remoteDir = path.join(remoteRoot, "opencode-reference-test") + const remoteRepo = path.join(remoteDir, "repo.git") + + yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "configured\n")) + yield* git(source, ["add", "."]) + yield* git(source, ["commit", "-m", "add readme"]) + yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie) + yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo]) + + const reference = yield* Reference.Service + yield* githubBase( + `file://${remoteRoot}/`, + Effect.gen(function* () { + yield* reference.init() + yield* waitForContent(fs, path.join(cache, "README.md"), "configured\n") + }), + ) + + expect(yield* fs.existsSafe(path.join(cache, ".git"))).toBe(true) + expect(yield* fs.readFileString(path.join(cache, "README.md"))).toBe("configured\n") + + const resolved = yield* reference.get("docs") + expect(resolved?.kind).toBe("git") + if (resolved?.kind === "git") expect(resolved.path).toBe(cache) + }), + { + config: { + reference: { + docs: "opencode-reference-test/repo", }, }, - ), + }, ), ) - it.live("refreshes configured git references on new instance init", () => - experimentalScout( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-refresh", "repo") - yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore) - yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore)) - - const source = yield* tmpdirScoped({ git: true }) - const remoteRoot = yield* tmpdirScoped() - const remoteDir = path.join(remoteRoot, "opencode-reference-refresh") - const remoteRepo = path.join(remoteDir, "repo.git") - - yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v1\n")) - yield* git(source, ["add", "."]) - yield* git(source, ["commit", "-m", "add readme"]) - yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie) - yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo]) - - yield* githubBase( - `file://${remoteRoot}/`, - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const reference = yield* Reference.Service - yield* reference.init() - yield* waitForContent(fs, path.join(cache, "README.md"), "v1\n") - }), - { - config: { - reference: { - docs: "opencode-reference-refresh/repo", - }, + scout.live("refreshes configured git references on new instance init", () => + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-refresh", "repo") + yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore) + yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore)) + + const source = yield* tmpdirScoped({ git: true }) + const remoteRoot = yield* tmpdirScoped() + const remoteDir = path.join(remoteRoot, "opencode-reference-refresh") + const remoteRepo = path.join(remoteDir, "repo.git") + + yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v1\n")) + yield* git(source, ["add", "."]) + yield* git(source, ["commit", "-m", "add readme"]) + yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie) + yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo]) + + yield* githubBase( + `file://${remoteRoot}/`, + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const reference = yield* Reference.Service + yield* reference.init() + yield* waitForContent(fs, path.join(cache, "README.md"), "v1\n") + }), + { + config: { + reference: { + docs: "opencode-reference-refresh/repo", }, }, - ), - ) - - const branch = yield* git(source, ["branch", "--show-current"]) - yield* git(source, ["remote", "add", "origin", remoteRepo]) - yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v2\n")) - yield* git(source, ["add", "."]) - yield* git(source, ["commit", "-m", "update readme"]) - yield* git(source, ["push", "origin", `${branch}:${branch}`]) - - yield* githubBase( - `file://${remoteRoot}/`, - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const reference = yield* Reference.Service - yield* reference.init() - yield* waitForContent(fs, path.join(cache, "README.md"), "v2\n") - }), - { - config: { - reference: { - docs: "opencode-reference-refresh/repo", - }, + }, + ), + ) + + const branch = yield* git(source, ["branch", "--show-current"]) + yield* git(source, ["remote", "add", "origin", remoteRepo]) + yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v2\n")) + yield* git(source, ["add", "."]) + yield* git(source, ["commit", "-m", "update readme"]) + yield* git(source, ["push", "origin", `${branch}:${branch}`]) + + yield* githubBase( + `file://${remoteRoot}/`, + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const reference = yield* Reference.Service + yield* reference.init() + yield* waitForContent(fs, path.join(cache, "README.md"), "v2\n") + }), + { + config: { + reference: { + docs: "opencode-reference-refresh/repo", }, }, - ), - ) - }), - ), + }, + ), + ) + }), ) }) diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 11bb1513f..fcbd10bb4 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -4,8 +4,10 @@ import path from "path" import { Agent } from "../../src/agent/agent" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" +import { Config } from "@/config/config" +import { RuntimeFlags } from "@/effect/runtime-flags" +import { Git } from "@/git" import { LSP } from "@/lsp/lsp" import { Permission } from "../../src/permission" import { Instance } from "../../src/project/instance" @@ -36,17 +38,27 @@ const ctx = { ask: () => Effect.void, } -const it = testEffect( +const referenceLayer = (flags: Partial = {}) => + Reference.layer.pipe( + Layer.provide(Config.defaultLayer), + Layer.provide(AppFileSystem.defaultLayer), + Layer.provide(Git.defaultLayer), + Layer.provide(RuntimeFlags.layer(flags)), + ) + +const readLayer = (flags: Partial = {}) => Layer.mergeAll( Agent.defaultLayer, AppFileSystem.defaultLayer, CrossSpawnSpawner.defaultLayer, Instruction.defaultLayer, LSP.defaultLayer, - Reference.defaultLayer, + referenceLayer(flags), Truncate.defaultLayer, - ), -) + ) + +const it = testEffect(readLayer()) +const scout = testEffect(readLayer({ experimentalScout: true })) const init = Effect.fn("ReadToolTest.init")(function* () { const info = yield* ReadTool @@ -85,19 +97,6 @@ const fail = Effect.fn("ReadToolTest.fail")(function* ( const full = (p: string) => (process.platform === "win32" ? Filesystem.normalizePath(p) : p) const glob = (p: string) => process.platform === "win32" ? Filesystem.normalizePathPattern(p) : p.replaceAll("\\", "/") -const experimentalScout = (self: Effect.Effect) => - Effect.acquireUseRelease( - Effect.sync(() => { - const previous = Flag.OPENCODE_EXPERIMENTAL_SCOUT - Flag.OPENCODE_EXPERIMENTAL_SCOUT = true - return previous - }), - () => self, - (previous) => - Effect.sync(() => { - Flag.OPENCODE_EXPERIMENTAL_SCOUT = previous - }), - ) const githubBase = (url: string, self: Effect.Effect) => Effect.acquireUseRelease( Effect.sync(() => { @@ -260,44 +259,42 @@ describe("tool.read external_directory permission", () => { }), ) - it.live("does not ask for external_directory permission when reading configured references", () => - experimentalScout( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo") - yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore) - yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore)) - - const source = yield* tmpdirScoped({ git: true }) - const remoteRoot = yield* tmpdirScoped() - const remoteDir = path.join(remoteRoot, "opencode-read-reference") - const remoteRepo = path.join(remoteDir, "repo.git") - yield* put(path.join(source, "notes.md"), "reference notes") - yield* git(source, ["add", "."]) - yield* git(source, ["commit", "-m", "add notes"]) - yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie) - yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo]) - - const dir = yield* tmpdirScoped({ - git: true, - config: { - reference: { - docs: "opencode-read-reference/repo", - }, + scout.live("does not ask for external_directory permission when reading configured references", () => + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo") + yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore) + yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore)) + + const source = yield* tmpdirScoped({ git: true }) + const remoteRoot = yield* tmpdirScoped() + const remoteDir = path.join(remoteRoot, "opencode-read-reference") + const remoteRepo = path.join(remoteDir, "repo.git") + yield* put(path.join(source, "notes.md"), "reference notes") + yield* git(source, ["add", "."]) + yield* git(source, ["commit", "-m", "add notes"]) + yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie) + yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo]) + + const dir = yield* tmpdirScoped({ + git: true, + config: { + reference: { + docs: "opencode-read-reference/repo", }, - }) + }, + }) - const { items, next } = asks() - const result = yield* githubBase( - `file://${remoteRoot}/`, - exec(dir, { filePath: path.join(cache, "notes.md") }, next), - ) - const ext = items.find((item) => item.permission === "external_directory") + const { items, next } = asks() + const result = yield* githubBase( + `file://${remoteRoot}/`, + exec(dir, { filePath: path.join(cache, "notes.md") }, next), + ) + const ext = items.find((item) => item.permission === "external_directory") - expect(result.output).toContain("reference notes") - expect(ext).toBeUndefined() - }), - ), + expect(result.output).toContain("reference notes") + expect(ext).toBeUndefined() + }), ) }) From 098bdd8ae2c26d2414fa6fe1ff1672c8ce42c45b Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 17:17:04 +0530 Subject: [PATCH 252/378] refactor(flags): route plan mode through runtime flags (#27320) --- packages/opencode/src/session/prompt.ts | 5 ++++- packages/opencode/test/session/prompt.test.ts | 1 + packages/opencode/test/session/snapshot-tool-race.test.ts | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 8656050fa..3b1f2c41a 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -51,6 +51,7 @@ import { InstanceState } from "@/effect/instance-state" import { TaskTool, type TaskPromptOps } from "@/tool/task" import { SessionRunState } from "./run-state" import { EffectBridge } from "@/effect/bridge" +import { RuntimeFlags } from "@/effect/runtime-flags" import { SyncEvent } from "@/sync" import { SessionEvent } from "@/v2/session-event" import { Modelv2 } from "@/v2/model" @@ -202,6 +203,7 @@ export const layer = Layer.effect( const llm = yield* LLM.Service const references = yield* Reference.Service const sync = yield* SyncEvent.Service + const flags = yield* RuntimeFlags.Service const runner = Effect.fn("SessionPrompt.runner")(function* () { return yield* EffectBridge.make() }) @@ -384,7 +386,7 @@ export const layer = Layer.effect( const userMessage = input.messages.findLast((msg) => msg.info.role === "user") if (!userMessage) return input.messages - if (!Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE) { + if (!flags.experimentalPlanMode) { if (input.agent.name === "plan") { userMessage.parts.push({ id: PartID.ascending(), @@ -2024,6 +2026,7 @@ export const defaultLayer = Layer.suspend(() => Bus.layer, CrossSpawnSpawner.defaultLayer, SyncEvent.defaultLayer, + RuntimeFlags.defaultLayer, ), ), ), diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 427ce17e8..de548c926 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -206,6 +206,7 @@ function makeHttp() { Layer.provideMerge(trunc), Layer.provide(Instruction.defaultLayer), Layer.provide(SystemPrompt.defaultLayer), + Layer.provide(RuntimeFlags.layer()), Layer.provideMerge(deps), ), ).pipe(Layer.provide(summary)) diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index adf892687..49632a829 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -165,6 +165,7 @@ function makeHttp() { Layer.provideMerge(trunc), Layer.provide(Instruction.defaultLayer), Layer.provide(SystemPrompt.defaultLayer), + Layer.provide(RuntimeFlags.layer()), Layer.provideMerge(deps), ), ) From f13fc5a8a83658aaba7f298033500726d82a875d Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 17:44:09 +0530 Subject: [PATCH 253/378] refactor(flags): route event system through runtime flags (#27323) --- .../src/cli/cmd/tui/plugin/internal.ts | 32 ++++++++++--------- .../src/cli/cmd/tui/plugin/runtime.ts | 9 ++++-- packages/opencode/src/effect/runtime-flags.ts | 1 + packages/opencode/src/session/compaction.ts | 9 ++++-- packages/opencode/src/session/processor.ts | 31 ++++++++++-------- packages/opencode/src/session/prompt.ts | 9 +++--- .../test/effect/runtime-flags.test.ts | 1 + .../opencode/test/session/compaction.test.ts | 4 +++ .../test/session/processor-effect.test.ts | 8 ++++- packages/opencode/test/session/prompt.test.ts | 11 +++++-- .../test/session/snapshot-tool-race.test.ts | 11 +++++-- 11 files changed, 80 insertions(+), 46 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts index eaa9dfb32..5e0aec3ba 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts @@ -11,7 +11,7 @@ import Notifications from "../feature-plugins/system/notifications" import SessionV2Debug from "../feature-plugins/system/session-v2" import WhichKey from "../feature-plugins/system/which-key" import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui" -import { Flag } from "@opencode-ai/core/flag/flag" +import type { RuntimeFlags } from "@/effect/runtime-flags" export type InternalTuiPlugin = Omit & { id: string @@ -19,17 +19,19 @@ export type InternalTuiPlugin = Omit & { enabled?: boolean } -export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [ - HomeFooter, - HomeTips, - SidebarContext, - SidebarMcp, - SidebarLsp, - SidebarTodo, - SidebarFiles, - SidebarFooter, - Notifications, - PluginManager, - WhichKey, - ...(Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM ? [SessionV2Debug] : []), -] +export function internalTuiPlugins(flags: Pick): InternalTuiPlugin[] { + return [ + HomeFooter, + HomeTips, + SidebarContext, + SidebarMcp, + SidebarLsp, + SidebarTodo, + SidebarFiles, + SidebarFooter, + Notifications, + PluginManager, + WhichKey, + ...(flags.experimentalEventSystem ? [SessionV2Debug] : []), + ] +} diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index 4af16d2b8..420826ad0 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -35,11 +35,13 @@ import { Filesystem } from "@/util/filesystem" import { Process } from "@/util/process" import { Flock } from "@opencode-ai/core/util/flock" import { Flag } from "@opencode-ai/core/flag/flag" -import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal" +import { internalTuiPlugins, type InternalTuiPlugin } from "./internal" import { setupSlots, Slot as View } from "./slots" import type { HostPluginApi, HostSlots } from "./slots" import { ConfigPlugin } from "@/config/plugin" import { createCommandShim } from "./command-shim" +import { RuntimeFlags } from "@/effect/runtime-flags" +import { Effect } from "effect" ensureRuntimePluginSupport({ additional: keymapRuntimeModules }) @@ -1070,7 +1072,10 @@ async function load(input: { api: Api; config: TuiConfig.Resolved; dispose?: () log.info("skipping external tui plugins in pure mode", { count: config.plugin_origins.length }) } - for (const item of INTERNAL_TUI_PLUGINS) { + const flags = await Effect.runPromise( + RuntimeFlags.Service.use((flags) => Effect.succeed(flags)).pipe(Effect.provide(RuntimeFlags.defaultLayer)), + ) + for (const item of internalTuiPlugins(flags)) { log.info("loading internal tui plugin", { id: item.id }) const entry = loadInternalPlugin(item) const meta = createMeta(entry.source, entry.spec, entry.target, undefined, entry.id) diff --git a/packages/opencode/src/effect/runtime-flags.ts b/packages/opencode/src/effect/runtime-flags.ts index 3dfb2e99f..b1b8ab25a 100644 --- a/packages/opencode/src/effect/runtime-flags.ts +++ b/packages/opencode/src/effect/runtime-flags.ts @@ -22,6 +22,7 @@ export class Service extends ConfigService.Service()("@opencode/Runtime experimentalScout: enabledByExperimental("OPENCODE_EXPERIMENTAL_SCOUT"), experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"), experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"), + experimentalEventSystem: enabledByExperimental("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"), client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")), }) {} diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index f3c160fe7..3340e0fbe 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -18,9 +18,9 @@ import { InstanceState } from "@/effect/instance-state" import { isOverflow as overflow, usable } from "./overflow" import { makeRuntime } from "@/effect/run-service" import { serviceUse } from "@/effect/service-use" +import { RuntimeFlags } from "@/effect/runtime-flags" import { SyncEvent } from "@/sync" import { SessionEvent } from "@/v2/session-event" -import { Flag } from "@opencode-ai/core/flag/flag" const log = Log.create({ service: "session.compaction" }) @@ -221,6 +221,7 @@ export const layer: Layer.Layer< | SessionProcessor.Service | Provider.Service | SyncEvent.Service + | RuntimeFlags.Service > = Layer.effect( Service, Effect.gen(function* () { @@ -232,6 +233,7 @@ export const layer: Layer.Layer< const processors = yield* SessionProcessor.Service const provider = yield* Provider.Service const sync = yield* SyncEvent.Service + const flags = yield* RuntimeFlags.Service const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: { tokens: MessageV2.Assistant["tokens"] @@ -572,7 +574,7 @@ export const layer: Layer.Layer< parts: [], }, ) - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Compaction.Ended.Sync, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), @@ -608,7 +610,7 @@ export const layer: Layer.Layer< auto: input.auto, overflow: input.overflow, }) - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Compaction.Started.Sync, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), @@ -636,6 +638,7 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(Bus.layer), Layer.provide(Config.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(RuntimeFlags.defaultLayer), ), ) diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 506ec0c40..c731239b6 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -25,7 +25,7 @@ import { SyncEvent } from "@/sync" import { SessionEvent } from "@/v2/session-event" import { Modelv2 } from "@/v2/model" import * as DateTime from "effect/DateTime" -import { Flag } from "@opencode-ai/core/flag/flag" +import { RuntimeFlags } from "@/effect/runtime-flags" const DOOM_LOOP_THRESHOLD = 3 const log = Log.create({ service: "session.processor" }) @@ -98,6 +98,7 @@ export const layer: Layer.Layer< | SessionSummary.Service | SessionStatus.Service | SyncEvent.Service + | RuntimeFlags.Service > = Layer.effect( Service, Effect.gen(function* () { @@ -114,6 +115,7 @@ export const layer: Layer.Layer< const status = yield* SessionStatus.Service const image = yield* Image.Service const sync = yield* SyncEvent.Service + const flags = yield* RuntimeFlags.Service const create = Effect.fn("SessionProcessor.create")(function* (input: Input) { // Pre-capture snapshot before the LLM stream starts. The AI SDK @@ -232,7 +234,7 @@ export const layer: Layer.Layer< case "reasoning-start": if (value.id in ctx.reasoningMap) return // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Reasoning.Started.Sync, { sessionID: ctx.sessionID, reasoningID: value.id, @@ -267,7 +269,7 @@ export const layer: Layer.Layer< case "reasoning-end": if (!(value.id in ctx.reasoningMap)) return // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Reasoning.Ended.Sync, { sessionID: ctx.sessionID, reasoningID: value.id, @@ -288,7 +290,7 @@ export const layer: Layer.Layer< throw new Error(`Tool call not allowed while generating summary: ${value.toolName}`) } // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Tool.Input.Started.Sync, { sessionID: ctx.sessionID, callID: value.id, @@ -319,7 +321,7 @@ export const layer: Layer.Layer< case "tool-input-end": { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Tool.Input.Ended.Sync, { sessionID: ctx.sessionID, callID: value.id, @@ -336,7 +338,7 @@ export const layer: Layer.Layer< } const toolCall = yield* readToolCall(value.toolCallId) // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Tool.Called.Sync, { sessionID: ctx.sessionID, callID: value.toolCallId, @@ -422,7 +424,7 @@ export const layer: Layer.Layer< attachments: attachments?.length ? attachments : undefined, } // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Tool.Success.Sync, { sessionID: ctx.sessionID, callID: value.toolCallId, @@ -452,7 +454,7 @@ export const layer: Layer.Layer< case "tool-error": { const toolCall = yield* readToolCall(value.toolCallId) // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Tool.Failed.Sync, { sessionID: ctx.sessionID, callID: value.toolCallId, @@ -477,7 +479,7 @@ export const layer: Layer.Layer< if (!ctx.snapshot) ctx.snapshot = yield* snapshot.track() if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Step.Started.Sync, { sessionID: ctx.sessionID, agent: input.assistantMessage.agent, @@ -509,7 +511,7 @@ export const layer: Layer.Layer< }) if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Step.Ended.Sync, { sessionID: ctx.sessionID, finish: value.finishReason, @@ -566,7 +568,7 @@ export const layer: Layer.Layer< case "text-start": if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Text.Started.Sync, { sessionID: ctx.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), @@ -613,7 +615,7 @@ export const layer: Layer.Layer< )).text if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Text.Ended.Sync, { sessionID: ctx.sessionID, text: ctx.currentText.text, @@ -709,7 +711,7 @@ export const layer: Layer.Layer< } if (!ctx.assistantMessage.summary) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Step.Failed.Sync, { sessionID: ctx.sessionID, error: { @@ -763,7 +765,7 @@ export const layer: Layer.Layer< parse, set: (info) => { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - const event = Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM + const event = flags.experimentalEventSystem ? sync.run(SessionEvent.Retried.Sync, { sessionID: ctx.sessionID, attempt: info.attempt, @@ -826,6 +828,7 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(Bus.layer), Layer.provide(Config.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(RuntimeFlags.defaultLayer), ), ) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 3b1f2c41a..cae0dd384 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -23,7 +23,6 @@ import { ToolRegistry } from "@/tool/registry" import { ToolJsonSchema } from "@/tool/json-schema" import { MCP } from "../mcp" import { LSP } from "@/lsp/lsp" -import { Flag } from "@opencode-ai/core/flag/flag" import { ulid } from "ulid" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" @@ -957,7 +956,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the }, } yield* sessions.updatePart(part) - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Shell.Started.Sync, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(started), @@ -980,7 +979,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the output += "\n\n" + ["", "User aborted the command", ""].join("\n") } const completed = Date.now() - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Shell.Ended.Sync, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(completed), @@ -1571,7 +1570,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the }, ) // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Prompted.Sync, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(info.time.created), @@ -1585,7 +1584,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the } for (const text of nextPrompt.synthetic) { // TODO(v2): Temporary dual-write while migrating session messages to v2 events. - if (Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM) { + if (flags.experimentalEventSystem) { yield* sync.run(SessionEvent.Synthetic.Sync, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(info.time.created), diff --git a/packages/opencode/test/effect/runtime-flags.test.ts b/packages/opencode/test/effect/runtime-flags.test.ts index 058fa7559..c21399017 100644 --- a/packages/opencode/test/effect/runtime-flags.test.ts +++ b/packages/opencode/test/effect/runtime-flags.test.ts @@ -33,6 +33,7 @@ describe("RuntimeFlags", () => { expect(flags.experimentalScout).toBe(true) expect(flags.experimentalLspTool).toBe(true) expect(flags.experimentalPlanMode).toBe(true) + expect(flags.experimentalEventSystem).toBe(true) expect(flags.client).toBe("desktop") }), ) diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 2b623511b..d8a416790 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -28,6 +28,7 @@ import { testEffect } from "../lib/effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { TestConfig } from "../fixture/config" import { SyncEvent } from "@/sync" +import { RuntimeFlags } from "@/effect/runtime-flags" void Log.init({ print: false }) @@ -225,6 +226,7 @@ const deps = Layer.mergeAll( Bus.layer, Config.defaultLayer, SyncEvent.defaultLayer, + RuntimeFlags.layer({ experimentalEventSystem: true }), ) const env = Layer.mergeAll( @@ -257,6 +259,7 @@ function compactionProcessLayer(options?: CompactionProcessOptions) { ? SessionProcessorModule.SessionProcessor.layer.pipe( Layer.provide(summary), Layer.provide(Image.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), Layer.provide(status), ) : layer(options?.result ?? "continue") @@ -272,6 +275,7 @@ function compactionProcessLayer(options?: CompactionProcessOptions) { Layer.provide(bus), Layer.provide(options?.config ?? Config.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), ) } diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index fc5e73877..537665b9a 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -25,6 +25,7 @@ import { provideTmpdirServer } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { raw, reply, TestLLMServer } from "../lib/llm-server" import { SyncEvent } from "@/sync" +import { RuntimeFlags } from "@/effect/runtime-flags" void Log.init({ print: false }) @@ -171,7 +172,12 @@ const deps = Layer.mergeAll( ).pipe(Layer.provideMerge(infra)) const env = Layer.mergeAll( TestLLMServer.layer, - SessionProcessor.layer.pipe(Layer.provide(summary), Layer.provide(Image.defaultLayer), Layer.provideMerge(deps)), + SessionProcessor.layer.pipe( + Layer.provide(summary), + Layer.provide(Image.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), + Layer.provideMerge(deps), + ), ) const it = testEffect(env) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index de548c926..c3d113d52 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -180,7 +180,7 @@ function makeHttp() { Layer.provide(Reference.defaultLayer), Layer.provide(Ripgrep.defaultLayer), Layer.provide(Format.defaultLayer), - Layer.provide(RuntimeFlags.layer()), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), Layer.provideMerge(todo), Layer.provideMerge(question), Layer.provideMerge(deps), @@ -189,9 +189,14 @@ function makeHttp() { const proc = SessionProcessor.layer.pipe( Layer.provide(summary), Layer.provide(Image.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), + Layer.provideMerge(deps), + ) + const compact = SessionCompaction.layer.pipe( + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), + Layer.provideMerge(proc), Layer.provideMerge(deps), ) - const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) return Layer.mergeAll( TestLLMServer.layer, SessionPrompt.layer.pipe( @@ -206,7 +211,7 @@ function makeHttp() { Layer.provideMerge(trunc), Layer.provide(Instruction.defaultLayer), Layer.provide(SystemPrompt.defaultLayer), - Layer.provide(RuntimeFlags.layer()), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), Layer.provideMerge(deps), ), ).pipe(Layer.provide(summary)) diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 49632a829..28565699b 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -138,7 +138,7 @@ function makeHttp() { Layer.provide(Reference.defaultLayer), Layer.provide(Ripgrep.defaultLayer), Layer.provide(Format.defaultLayer), - Layer.provide(RuntimeFlags.layer()), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), Layer.provideMerge(todo), Layer.provideMerge(question), Layer.provideMerge(deps), @@ -147,9 +147,14 @@ function makeHttp() { const proc = SessionProcessor.layer.pipe( Layer.provide(SessionSummary.defaultLayer), Layer.provide(Image.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), + Layer.provideMerge(deps), + ) + const compact = SessionCompaction.layer.pipe( + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), + Layer.provideMerge(proc), Layer.provideMerge(deps), ) - const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) return Layer.mergeAll( TestLLMServer.layer, SessionSummary.defaultLayer, @@ -165,7 +170,7 @@ function makeHttp() { Layer.provideMerge(trunc), Layer.provide(Instruction.defaultLayer), Layer.provide(SystemPrompt.defaultLayer), - Layer.provide(RuntimeFlags.layer()), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), Layer.provideMerge(deps), ), ) From 762020f63a3ff69631c93b593c28a267bc7aba59 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 09:56:15 -0400 Subject: [PATCH 254/378] chore: delete unused util/network module (#27329) --- packages/opencode/src/util/network.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 packages/opencode/src/util/network.ts diff --git a/packages/opencode/src/util/network.ts b/packages/opencode/src/util/network.ts deleted file mode 100644 index 69e5d1758..000000000 --- a/packages/opencode/src/util/network.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function online() { - const nav = globalThis.navigator - if (!nav || typeof nav.onLine !== "boolean") return true - return nav.onLine -} - -export function proxied() { - return !!(process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy) -} From bc25342f3400280b666d446ad455107a180d213a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 10:01:19 -0400 Subject: [PATCH 255/378] chore: delete util/scrap module (#27330) --- packages/opencode/src/util/scrap.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 packages/opencode/src/util/scrap.ts diff --git a/packages/opencode/src/util/scrap.ts b/packages/opencode/src/util/scrap.ts deleted file mode 100644 index 554dba1c5..000000000 --- a/packages/opencode/src/util/scrap.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const foo: string = "42" -export const bar: number = 123 - -export function dummyFunction(): void { - console.log("This is a dummy function") -} - -export function randomHelper(): boolean { - return Math.random() > 0.5 -} From bebe5442a5c2395f2eafe9d7dca463e453532e52 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 10:02:17 -0400 Subject: [PATCH 256/378] chore: delete unused util/color module (#27331) --- packages/opencode/src/util/color.ts | 19 ------------------- .../opencode/test/config/agent-color.test.ts | 16 +--------------- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 packages/opencode/src/util/color.ts diff --git a/packages/opencode/src/util/color.ts b/packages/opencode/src/util/color.ts deleted file mode 100644 index 3752fd19b..000000000 --- a/packages/opencode/src/util/color.ts +++ /dev/null @@ -1,19 +0,0 @@ -export function isValidHex(hex?: string): hex is string { - if (!hex) return false - return /^#[0-9a-fA-F]{6}$/.test(hex) -} - -export function hexToRgb(hex: string): { r: number; g: number; b: number } { - const r = parseInt(hex.slice(1, 3), 16) - const g = parseInt(hex.slice(3, 5), 16) - const b = parseInt(hex.slice(5, 7), 16) - return { r, g, b } -} - -export function hexToAnsiBold(hex?: string): string | undefined { - if (!isValidHex(hex)) return undefined - const { r, g, b } = hexToRgb(hex) - return `\x1b[38;2;${r};${g};${b}m\x1b[1m` -} - -export * as Color from "./color" diff --git a/packages/opencode/test/config/agent-color.test.ts b/packages/opencode/test/config/agent-color.test.ts index 369b3a1fd..d19808059 100644 --- a/packages/opencode/test/config/agent-color.test.ts +++ b/packages/opencode/test/config/agent-color.test.ts @@ -1,9 +1,8 @@ -import { test, expect } from "bun:test" +import { expect } from "bun:test" import { Effect, Layer } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Config } from "@/config/config" import { Agent as AgentSvc } from "../../src/agent/agent" -import { Color } from "@/util/color" import { testEffect } from "../lib/effect" const it = testEffect(Layer.mergeAll(Config.defaultLayer, AgentSvc.defaultLayer, CrossSpawnSpawner.defaultLayer)) @@ -46,16 +45,3 @@ it.instance( }, }, ) - -test("Color.hexToAnsiBold converts valid hex to ANSI", () => { - const result = Color.hexToAnsiBold("#FFA500") - expect(result).toBe("\x1b[38;2;255;165;0m\x1b[1m") -}) - -test("Color.hexToAnsiBold returns undefined for invalid hex", () => { - expect(Color.hexToAnsiBold(undefined)).toBeUndefined() - expect(Color.hexToAnsiBold("")).toBeUndefined() - expect(Color.hexToAnsiBold("#FFF")).toBeUndefined() - expect(Color.hexToAnsiBold("FFA500")).toBeUndefined() - expect(Color.hexToAnsiBold("#GGGGGG")).toBeUndefined() -}) From 8345152319b8c4e38431ec7c0cd85c2caafd9338 Mon Sep 17 00:00:00 2001 From: Dax Date: Wed, 13 May 2026 10:43:08 -0400 Subject: [PATCH 257/378] core: expose v2 model listing API (#25821) --- AGENTS.md | 23 + bun.lock | 35 +- packages/core/package.json | 32 +- packages/core/src/aisdk.ts | 172 +++++++ .../{opencode/src/v2 => core/src}/auth.ts | 48 +- packages/core/src/catalog.ts | 258 ++++++++++ .../src/github-copilot}/README.md | 0 ...vert-to-openai-compatible-chat-messages.ts | 0 .../chat/get-response-metadata.ts | 0 .../map-openai-compatible-finish-reason.ts | 0 .../chat/openai-compatible-api-types.ts | 0 .../openai-compatible-chat-language-model.ts | 0 .../chat/openai-compatible-chat-options.ts | 0 .../openai-compatible-metadata-extractor.ts | 0 .../chat/openai-compatible-prepare-tools.ts | 0 .../src/github-copilot}/copilot-provider.ts | 0 .../openai-compatible-error.ts | 0 .../convert-to-openai-responses-input.ts | 0 .../map-openai-responses-finish-reason.ts | 0 .../responses/openai-config.ts | 0 .../github-copilot}/responses/openai-error.ts | 0 .../responses/openai-responses-api-types.ts | 0 .../openai-responses-language-model.ts | 0 .../openai-responses-prepare-tools.ts | 0 .../responses/openai-responses-settings.ts | 0 .../responses/tool/code-interpreter.ts | 0 .../responses/tool/file-search.ts | 0 .../responses/tool/image-generation.ts | 0 .../responses/tool/local-shell.ts | 0 .../responses/tool/web-search-preview.ts | 0 .../responses/tool/web-search.ts | 0 packages/core/src/model.ts | 116 +++++ packages/core/src/plugin.ts | 146 ++++++ packages/core/src/plugin/auth.ts | 27 + packages/core/src/plugin/env.ts | 18 + packages/core/src/plugin/provider.ts | 1 + packages/core/src/plugin/provider/alibaba.ts | 15 + .../src/plugin/provider/amazon-bedrock.ts | 94 ++++ .../core/src/plugin/provider/anthropic.ts | 21 + packages/core/src/plugin/provider/azure.ts | 67 +++ packages/core/src/plugin/provider/cerebras.ts | 20 + .../plugin/provider/cloudflare-ai-gateway.ts | 81 +++ .../plugin/provider/cloudflare-workers-ai.ts | 69 +++ packages/core/src/plugin/provider/cohere.ts | 15 + .../core/src/plugin/provider/deepinfra.ts | 15 + packages/core/src/plugin/provider/dynamic.ts | 31 ++ packages/core/src/plugin/provider/gateway.ts | 15 + .../src/plugin/provider/github-copilot.ts | 44 ++ packages/core/src/plugin/provider/gitlab.ts | 64 +++ .../core/src/plugin/provider/google-vertex.ts | 124 +++++ packages/core/src/plugin/provider/google.ts | 15 + packages/core/src/plugin/provider/groq.ts | 15 + packages/core/src/plugin/provider/index.ts | 67 +++ packages/core/src/plugin/provider/kilo.ts | 16 + .../core/src/plugin/provider/llmgateway.ts | 18 + packages/core/src/plugin/provider/mistral.ts | 15 + packages/core/src/plugin/provider/nvidia.ts | 16 + .../src/plugin/provider/openai-compatible.ts | 17 + packages/core/src/plugin/provider/openai.ts | 27 + packages/core/src/plugin/provider/opencode.ts | 27 + .../core/src/plugin/provider/openrouter.ts | 29 ++ .../core/src/plugin/provider/perplexity.ts | 15 + .../core/src/plugin/provider/sap-ai-core.ts | 40 ++ .../core/src/plugin/provider/togetherai.ts | 15 + packages/core/src/plugin/provider/venice.ts | 15 + packages/core/src/plugin/provider/vercel.ts | 21 + packages/core/src/plugin/provider/xai.ts | 20 + packages/core/src/plugin/provider/zenmux.ts | 16 + packages/core/src/provider.ts | 120 +++++ .../src/v2 => core/src}/session-prompt.ts | 0 .../src/v2 => core/src}/tool-output.ts | 0 .../v2/schema.ts => core/src/v2-schema.ts} | 2 +- .../convert-to-copilot-messages.test.ts | 2 +- .../copilot-chat-model.test.ts | 2 +- .../plugin/provider-github-copilot.test.ts | 188 +++++++ packages/core/test/v2/catalog.test.ts | 199 ++++++++ .../v2/plugin/fixtures/provider-factory.ts | 9 + .../test/v2/plugin/provider-alibaba.test.ts | 67 +++ .../v2/plugin/provider-amazon-bedrock.test.ts | 464 ++++++++++++++++++ .../test/v2/plugin/provider-anthropic.test.ts | 91 ++++ .../provider-azure-cognitive-services.test.ts | 127 +++++ .../test/v2/plugin/provider-azure.test.ts | 245 +++++++++ .../test/v2/plugin/provider-cerebras.test.ts | 102 ++++ .../provider-cloudflare-ai-gateway.test.ts | 384 +++++++++++++++ .../provider-cloudflare-workers-ai.test.ts | 267 ++++++++++ .../test/v2/plugin/provider-cohere.test.ts | 86 ++++ .../test/v2/plugin/provider-deepinfra.test.ts | 129 +++++ .../test/v2/plugin/provider-dynamic.test.ts | 172 +++++++ .../test/v2/plugin/provider-gateway.test.ts | 87 ++++ .../test/v2/plugin/provider-gitlab.test.ts | 346 +++++++++++++ .../provider-google-vertex-anthropic.test.ts | 147 ++++++ .../v2/plugin/provider-google-vertex.test.ts | 300 +++++++++++ .../test/v2/plugin/provider-google.test.ts | 70 +++ .../core/test/v2/plugin/provider-groq.test.ts | 101 ++++ .../core/test/v2/plugin/provider-helper.ts | 100 ++++ .../core/test/v2/plugin/provider-kilo.test.ts | 90 ++++ .../v2/plugin/provider-llmgateway.test.ts | 63 +++ .../test/v2/plugin/provider-mistral.test.ts | 106 ++++ .../test/v2/plugin/provider-nvidia.test.ts | 41 ++ .../plugin/provider-openai-compatible.test.ts | 101 ++++ .../test/v2/plugin/provider-openai.test.ts | 100 ++++ .../test/v2/plugin/provider-opencode.test.ts | 195 ++++++++ .../v2/plugin/provider-openrouter.test.ts | 105 ++++ .../v2/plugin/provider-perplexity.test.ts | 107 ++++ .../v2/plugin/provider-sap-ai-core.test.ts | 127 +++++ .../v2/plugin/provider-togetherai.test.ts | 97 ++++ .../test/v2/plugin/provider-venice.test.ts | 86 ++++ .../test/v2/plugin/provider-vercel.test.ts | 62 +++ .../core/test/v2/plugin/provider-xai.test.ts | 115 +++++ .../test/v2/plugin/provider-zenmux.test.ts | 103 ++++ packages/opencode/package.json | 1 - packages/opencode/src/cli/cmd/debug/index.ts | 2 + packages/opencode/src/cli/cmd/debug/v2.ts | 40 ++ .../src/cli/cmd/tui/routes/session/index.tsx | 33 +- packages/opencode/src/provider/provider.ts | 26 +- .../routes/instance/httpapi/groups/v2.ts | 4 + .../instance/httpapi/groups/v2/model.ts | 24 + .../instance/httpapi/groups/v2/provider.ts | 38 ++ .../instance/httpapi/groups/v2/session.ts | 2 +- .../routes/instance/httpapi/handlers/v2.ts | 8 +- .../instance/httpapi/handlers/v2/model.ts | 12 + .../instance/httpapi/handlers/v2/provider.ts | 22 + packages/opencode/src/session/llm.ts | 1 - packages/opencode/src/session/processor.ts | 9 +- packages/opencode/src/session/prompt.ts | 11 +- packages/opencode/src/v2/model.ts | 193 -------- packages/opencode/src/v2/plugin-boot.ts | 50 ++ packages/opencode/src/v2/plugin/models-dev.ts | 108 ++++ .../src/v2/provider-parity-checklist.md | 95 ++++ packages/opencode/src/v2/session-event.ts | 12 +- packages/opencode/src/v2/session-message.ts | 10 +- packages/opencode/src/v2/session.ts | 35 +- .../test/server/httpapi-exercise/index.ts | 6 + .../test/server/httpapi-session.test.ts | 9 +- .../test/v2/session-message-updater.test.ts | 21 +- packages/sdk/js/src/v2/gen/sdk.gen.ts | 38 ++ packages/sdk/js/src/v2/gen/types.gen.ts | 91 ++++ specs/v2/provider-model.md | 329 +++++++++++++ 138 files changed, 8188 insertions(+), 302 deletions(-) create mode 100644 packages/core/src/aisdk.ts rename packages/{opencode/src/v2 => core/src}/auth.ts (86%) create mode 100644 packages/core/src/catalog.ts rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/README.md (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/chat/convert-to-openai-compatible-chat-messages.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/chat/get-response-metadata.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/chat/map-openai-compatible-finish-reason.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/chat/openai-compatible-api-types.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/chat/openai-compatible-chat-language-model.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/chat/openai-compatible-chat-options.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/chat/openai-compatible-metadata-extractor.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/chat/openai-compatible-prepare-tools.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/copilot-provider.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/openai-compatible-error.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/convert-to-openai-responses-input.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/map-openai-responses-finish-reason.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/openai-config.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/openai-error.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/openai-responses-api-types.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/openai-responses-language-model.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/openai-responses-prepare-tools.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/openai-responses-settings.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/tool/code-interpreter.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/tool/file-search.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/tool/image-generation.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/tool/local-shell.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/tool/web-search-preview.ts (100%) rename packages/{opencode/src/provider/sdk/copilot => core/src/github-copilot}/responses/tool/web-search.ts (100%) create mode 100644 packages/core/src/model.ts create mode 100644 packages/core/src/plugin.ts create mode 100644 packages/core/src/plugin/auth.ts create mode 100644 packages/core/src/plugin/env.ts create mode 100644 packages/core/src/plugin/provider.ts create mode 100644 packages/core/src/plugin/provider/alibaba.ts create mode 100644 packages/core/src/plugin/provider/amazon-bedrock.ts create mode 100644 packages/core/src/plugin/provider/anthropic.ts create mode 100644 packages/core/src/plugin/provider/azure.ts create mode 100644 packages/core/src/plugin/provider/cerebras.ts create mode 100644 packages/core/src/plugin/provider/cloudflare-ai-gateway.ts create mode 100644 packages/core/src/plugin/provider/cloudflare-workers-ai.ts create mode 100644 packages/core/src/plugin/provider/cohere.ts create mode 100644 packages/core/src/plugin/provider/deepinfra.ts create mode 100644 packages/core/src/plugin/provider/dynamic.ts create mode 100644 packages/core/src/plugin/provider/gateway.ts create mode 100644 packages/core/src/plugin/provider/github-copilot.ts create mode 100644 packages/core/src/plugin/provider/gitlab.ts create mode 100644 packages/core/src/plugin/provider/google-vertex.ts create mode 100644 packages/core/src/plugin/provider/google.ts create mode 100644 packages/core/src/plugin/provider/groq.ts create mode 100644 packages/core/src/plugin/provider/index.ts create mode 100644 packages/core/src/plugin/provider/kilo.ts create mode 100644 packages/core/src/plugin/provider/llmgateway.ts create mode 100644 packages/core/src/plugin/provider/mistral.ts create mode 100644 packages/core/src/plugin/provider/nvidia.ts create mode 100644 packages/core/src/plugin/provider/openai-compatible.ts create mode 100644 packages/core/src/plugin/provider/openai.ts create mode 100644 packages/core/src/plugin/provider/opencode.ts create mode 100644 packages/core/src/plugin/provider/openrouter.ts create mode 100644 packages/core/src/plugin/provider/perplexity.ts create mode 100644 packages/core/src/plugin/provider/sap-ai-core.ts create mode 100644 packages/core/src/plugin/provider/togetherai.ts create mode 100644 packages/core/src/plugin/provider/venice.ts create mode 100644 packages/core/src/plugin/provider/vercel.ts create mode 100644 packages/core/src/plugin/provider/xai.ts create mode 100644 packages/core/src/plugin/provider/zenmux.ts create mode 100644 packages/core/src/provider.ts rename packages/{opencode/src/v2 => core/src}/session-prompt.ts (100%) rename packages/{opencode/src/v2 => core/src}/tool-output.ts (100%) rename packages/{opencode/src/v2/schema.ts => core/src/v2-schema.ts} (88%) rename packages/{opencode/test/provider/copilot => core/test/github-copilot}/convert-to-copilot-messages.test.ts (99%) rename packages/{opencode/test/provider/copilot => core/test/github-copilot}/copilot-chat-model.test.ts (99%) create mode 100644 packages/core/test/plugin/provider-github-copilot.test.ts create mode 100644 packages/core/test/v2/catalog.test.ts create mode 100644 packages/core/test/v2/plugin/fixtures/provider-factory.ts create mode 100644 packages/core/test/v2/plugin/provider-alibaba.test.ts create mode 100644 packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts create mode 100644 packages/core/test/v2/plugin/provider-anthropic.test.ts create mode 100644 packages/core/test/v2/plugin/provider-azure-cognitive-services.test.ts create mode 100644 packages/core/test/v2/plugin/provider-azure.test.ts create mode 100644 packages/core/test/v2/plugin/provider-cerebras.test.ts create mode 100644 packages/core/test/v2/plugin/provider-cloudflare-ai-gateway.test.ts create mode 100644 packages/core/test/v2/plugin/provider-cloudflare-workers-ai.test.ts create mode 100644 packages/core/test/v2/plugin/provider-cohere.test.ts create mode 100644 packages/core/test/v2/plugin/provider-deepinfra.test.ts create mode 100644 packages/core/test/v2/plugin/provider-dynamic.test.ts create mode 100644 packages/core/test/v2/plugin/provider-gateway.test.ts create mode 100644 packages/core/test/v2/plugin/provider-gitlab.test.ts create mode 100644 packages/core/test/v2/plugin/provider-google-vertex-anthropic.test.ts create mode 100644 packages/core/test/v2/plugin/provider-google-vertex.test.ts create mode 100644 packages/core/test/v2/plugin/provider-google.test.ts create mode 100644 packages/core/test/v2/plugin/provider-groq.test.ts create mode 100644 packages/core/test/v2/plugin/provider-helper.ts create mode 100644 packages/core/test/v2/plugin/provider-kilo.test.ts create mode 100644 packages/core/test/v2/plugin/provider-llmgateway.test.ts create mode 100644 packages/core/test/v2/plugin/provider-mistral.test.ts create mode 100644 packages/core/test/v2/plugin/provider-nvidia.test.ts create mode 100644 packages/core/test/v2/plugin/provider-openai-compatible.test.ts create mode 100644 packages/core/test/v2/plugin/provider-openai.test.ts create mode 100644 packages/core/test/v2/plugin/provider-opencode.test.ts create mode 100644 packages/core/test/v2/plugin/provider-openrouter.test.ts create mode 100644 packages/core/test/v2/plugin/provider-perplexity.test.ts create mode 100644 packages/core/test/v2/plugin/provider-sap-ai-core.test.ts create mode 100644 packages/core/test/v2/plugin/provider-togetherai.test.ts create mode 100644 packages/core/test/v2/plugin/provider-venice.test.ts create mode 100644 packages/core/test/v2/plugin/provider-vercel.test.ts create mode 100644 packages/core/test/v2/plugin/provider-xai.test.ts create mode 100644 packages/core/test/v2/plugin/provider-zenmux.test.ts create mode 100644 packages/opencode/src/cli/cmd/debug/v2.ts create mode 100644 packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts create mode 100644 packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts create mode 100644 packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts create mode 100644 packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts delete mode 100644 packages/opencode/src/v2/model.ts create mode 100644 packages/opencode/src/v2/plugin-boot.ts create mode 100644 packages/opencode/src/v2/plugin/models-dev.ts create mode 100644 packages/opencode/src/v2/provider-parity-checklist.md create mode 100644 specs/v2/provider-model.md diff --git a/AGENTS.md b/AGENTS.md index 7913ddabd..0b1998ec5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,6 +73,29 @@ function foo() { } ``` +### Complex Logic + +When a function has several validation branches or supporting details, make the main function read as the happy path and move supporting details into small helpers below it. + +```ts +// Good +export function loadThing(input: unknown) { + const config = requireConfig(input) + const metadata = readMetadata(input) + return createThing({ config, metadata }) +} + +function requireConfig(input: unknown) { + ... +} +``` + +- Keep helpers close to the code they support, below the main export when that improves readability. +- Do not over-abstract simple expressions into many single-use helpers; extract only when it names a real concept like `requireConfig` or `readMetadata`. +- Do not return `Effect` from helpers unless they actually perform effectful work. Synchronous parsing, validation, and option building should stay synchronous. +- Prefer Effect schema helpers such as `Schema.UnknownFromJsonString` and `Schema.decodeUnknownOption` over manual `JSON.parse` wrapped in `Effect.try` when parsing untrusted JSON strings. +- Add comments for non-obvious constraints and surprising behavior, not for obvious assignments or control flow. + ### Schema Definitions (Drizzle) Use snake_case for field names so column names don't need to be redefined as strings. diff --git a/bun.lock b/bun.lock index e8ff7adaf..2a79552b9 100644 --- a/bun.lock +++ b/bun.lock @@ -197,22 +197,50 @@ "opencode": "./bin/opencode", }, "dependencies": { + "@ai-sdk/alibaba": "1.0.17", + "@ai-sdk/amazon-bedrock": "4.0.96", + "@ai-sdk/anthropic": "3.0.71", + "@ai-sdk/azure": "3.0.49", + "@ai-sdk/cerebras": "2.0.41", + "@ai-sdk/cohere": "3.0.27", + "@ai-sdk/deepinfra": "2.0.41", + "@ai-sdk/gateway": "3.0.104", + "@ai-sdk/google": "3.0.63", + "@ai-sdk/google-vertex": "4.0.112", + "@ai-sdk/groq": "3.0.31", + "@ai-sdk/mistral": "3.0.27", + "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai-compatible": "2.0.41", + "@ai-sdk/perplexity": "3.0.26", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.23", + "@ai-sdk/togetherai": "2.0.41", + "@ai-sdk/vercel": "2.0.39", + "@ai-sdk/xai": "3.0.82", + "@aws-sdk/credential-providers": "3.993.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", + "@openrouter/ai-sdk-provider": "2.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", + "ai-gateway-provider": "3.1.2", "cross-spawn": "catalog:", "effect": "catalog:", + "gitlab-ai-provider": "6.6.0", "glob": "13.0.5", + "google-auth-library": "10.5.0", + "immer": "11.1.4", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "semver": "^7.6.3", + "venice-ai-sdk-provider": "2.0.1", "xdg-basedir": "5.1.0", + "zod": "catalog:", }, "devDependencies": { "@tsconfig/bun": "catalog:", @@ -380,7 +408,6 @@ "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", - "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", "@ai-sdk/xai": "3.0.82", @@ -5423,6 +5450,12 @@ "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], + "@opencode-ai/core/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="], + + "@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], + + "@opencode-ai/core/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], "@opencode-ai/desktop/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], diff --git a/packages/core/package.json b/packages/core/package.json index 6bcef68dc..4c47fea8b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,6 +26,27 @@ "@types/semver": "catalog:" }, "dependencies": { + "@ai-sdk/alibaba": "1.0.17", + "@ai-sdk/amazon-bedrock": "4.0.96", + "@ai-sdk/anthropic": "3.0.71", + "@ai-sdk/azure": "3.0.49", + "@ai-sdk/cerebras": "2.0.41", + "@ai-sdk/cohere": "3.0.27", + "@ai-sdk/deepinfra": "2.0.41", + "@ai-sdk/gateway": "3.0.104", + "@ai-sdk/google": "3.0.63", + "@ai-sdk/google-vertex": "4.0.112", + "@ai-sdk/groq": "3.0.31", + "@ai-sdk/mistral": "3.0.27", + "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai-compatible": "2.0.41", + "@ai-sdk/perplexity": "3.0.26", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.23", + "@ai-sdk/togetherai": "2.0.41", + "@ai-sdk/vercel": "2.0.39", + "@ai-sdk/xai": "3.0.82", + "@aws-sdk/credential-providers": "3.993.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@npmcli/arborist": "9.4.0", @@ -34,14 +55,21 @@ "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", - "effect": "catalog:", + "@openrouter/ai-sdk-provider": "2.8.1", + "ai-gateway-provider": "3.1.2", "cross-spawn": "catalog:", + "effect": "catalog:", + "gitlab-ai-provider": "6.6.0", "glob": "13.0.5", + "google-auth-library": "10.5.0", + "immer": "11.1.4", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "semver": "^7.6.3", - "xdg-basedir": "5.1.0" + "venice-ai-sdk-provider": "2.0.1", + "xdg-basedir": "5.1.0", + "zod": "catalog:" }, "overrides": { "drizzle-orm": "catalog:" diff --git a/packages/core/src/aisdk.ts b/packages/core/src/aisdk.ts new file mode 100644 index 000000000..5fa229430 --- /dev/null +++ b/packages/core/src/aisdk.ts @@ -0,0 +1,172 @@ +export * as AISDK from "./aisdk" + +import type { LanguageModelV3 } from "@ai-sdk/provider" +import { Cause, Context, Effect, Layer, Schema } from "effect" +import { ModelV2 } from "./model" +import { PluginV2 } from "./plugin" +import { ProviderV2 } from "./provider" + +type SDK = any + +function wrapSSE(res: Response, ms: number, ctl: AbortController) { + if (typeof ms !== "number" || ms <= 0) return res + if (!res.body) return res + if (!res.headers.get("content-type")?.includes("text/event-stream")) return res + + const reader = res.body.getReader() + const body = new ReadableStream({ + async pull(ctrl) { + const part = await new Promise>>((resolve, reject) => { + const id = setTimeout(() => { + const err = new Error("SSE read timed out") + ctl.abort(err) + void reader.cancel(err) + reject(err) + }, ms) + + reader.read().then( + (part) => { + clearTimeout(id) + resolve(part) + }, + (err) => { + clearTimeout(id) + reject(err) + }, + ) + }) + + if (part.done) { + ctrl.close() + return + } + + ctrl.enqueue(part.value) + }, + async cancel(reason) { + ctl.abort(reason) + await reader.cancel(reason) + }, + }) + + return new Response(body, { + headers: new Headers(res.headers), + status: res.status, + statusText: res.statusText, + }) +} + +function prepareOptions(model: ModelV2.Info, pkg: string) { + const options: Record = { name: model.providerID, ...model.options.aisdk.provider } + if (model.endpoint.type === "aisdk" && model.endpoint.url) options.baseURL = model.endpoint.url + + const customFetch = options.fetch + const chunkTimeout = options.chunkTimeout + delete options.chunkTimeout + options.fetch = async (input: Parameters[0], init?: RequestInit) => { + const opts = { ...(init ?? {}) } + const signals = [ + opts.signal, + typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined, + options.timeout !== undefined && options.timeout !== null && options.timeout !== false + ? AbortSignal.timeout(options.timeout) + : undefined, + ].filter((item): item is AbortSignal | AbortController => Boolean(item)) + const chunkAbortCtl = signals.find((item): item is AbortController => item instanceof AbortController) + const abortSignals = signals.map((item) => (item instanceof AbortController ? item.signal : item)) + if (abortSignals.length === 1) opts.signal = abortSignals[0] + if (abortSignals.length > 1) opts.signal = AbortSignal.any(abortSignals) + + if ((pkg === "@ai-sdk/openai" || pkg === "@ai-sdk/azure") && opts.body && opts.method === "POST") { + const body = JSON.parse(opts.body as string) + if (body.store !== true && Array.isArray(body.input)) { + for (const item of body.input) { + if ("id" in item) delete item.id + } + opts.body = JSON.stringify(body) + } + } + + const res = await (typeof customFetch === "function" ? customFetch : fetch)(input, { + ...opts, + timeout: false, + }) + if (!chunkAbortCtl || typeof chunkTimeout !== "number") return res + return wrapSSE(res, chunkTimeout, chunkAbortCtl) + } + + return options +} + +export class InitError extends Schema.TaggedErrorClass()("AISDK.InitError", { + providerID: ProviderV2.ID, + cause: Schema.Defect, +}) {} + +function initError(providerID: ProviderV2.ID) { + return Effect.catchCause((cause) => Effect.fail(new InitError({ providerID, cause: Cause.squash(cause) }))) +} + +export interface Interface { + readonly language: (model: ModelV2.Info) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/v2/AISDK") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const languages = new Map() + const sdks = new Map() + + return Service.of({ + language: Effect.fn("AISDK.language")(function* (model) { + const key = `${model.providerID}/${model.id}/${model.options.variant ?? "default"}` + const existing = languages.get(key) + if (existing) return existing + if (model.endpoint.type !== "aisdk") + return yield* new InitError({ + providerID: model.providerID, + cause: new Error(`Unsupported endpoint ${model.endpoint.type}`), + }) + + const options = prepareOptions(model, model.endpoint.package) + const sdkKey = JSON.stringify({ + providerID: model.providerID, + endpoint: model.endpoint, + options, + }) + const sdk = + sdks.get(sdkKey) ?? + (yield* plugin + .trigger("aisdk.sdk", { model, package: model.endpoint.package, options }, {}) + .pipe(initError(model.providerID))).sdk + if (!sdk) + return yield* new InitError({ + providerID: model.providerID, + cause: new Error("No AISDK provider plugin returned an SDK"), + }) + sdks.set(sdkKey, sdk) + const result = yield* plugin + .trigger( + "aisdk.language", + { + model, + sdk, + options, + }, + {}, + ) + .pipe(initError(model.providerID)) + const language = yield* Effect.sync(() => result.language ?? sdk.languageModel(model.apiID)).pipe( + initError(model.providerID), + ) + languages.set(key, language) + return language + }), + }) + }), +) + +export const defaultLayer = layer.pipe(Layer.provide(PluginV2.defaultLayer)) diff --git a/packages/opencode/src/v2/auth.ts b/packages/core/src/auth.ts similarity index 86% rename from packages/opencode/src/v2/auth.ts rename to packages/core/src/auth.ts index 0ac6223a6..843c9504b 100644 --- a/packages/opencode/src/v2/auth.ts +++ b/packages/core/src/auth.ts @@ -1,9 +1,9 @@ import path from "path" import { Effect, Layer, Option, Schema, Context, SynchronizedRef } from "effect" -import { Identifier } from "@opencode-ai/core/util/identifier" -import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema" -import { Global } from "@opencode-ai/core/global" -import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { Identifier } from "./util/identifier" +import { NonNegativeInt, withStatics } from "./schema" +import { Global } from "./global" +import { AppFileSystem } from "./filesystem" export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key" @@ -106,25 +106,43 @@ export const layer = Layer.effect( const fsys = yield* AppFileSystem.Service const global = yield* Global.Service const file = path.join(global.data, "auth-v2.json") + const legacyFile = path.join(global.data, "auth.json") + + const writeMigrated = Effect.fnUntraced(function* (raw: Record) { + const migrated = migrate(raw) + yield* fsys + .writeJson(file, migrated, 0o600) + .pipe(Effect.mapError((cause) => new AuthFileWriteError({ operation: "migrate", cause }))) + return migrated + }) + + const parseAuthContent = () => { + try { + return JSON.parse(process.env.OPENCODE_AUTH_CONTENT ?? "") + } catch {} + } const load: () => Effect.Effect = Effect.fnUntraced(function* () { if (process.env.OPENCODE_AUTH_CONTENT) { - try { - return JSON.parse(process.env.OPENCODE_AUTH_CONTENT) - } catch {} + const raw = parseAuthContent() + if (raw && typeof raw === "object") { + if ("version" in raw && raw.version === 2) return raw as Writable + return yield* writeMigrated(raw as Record) + } + return { version: 2, accounts: {}, active: {} } } - const raw = yield* fsys.readJson(file).pipe(Effect.orElseSucceed(() => null)) + const legacy = yield* fsys.readJson(legacyFile).pipe(Effect.orElseSucceed(() => null)) + if (legacy && typeof legacy === "object") return yield* writeMigrated(legacy as Record) - if (!raw || typeof raw !== "object") return { version: 2, accounts: {}, active: {} } + const raw = yield* fsys.readJson(file).pipe(Effect.orElseSucceed(() => null)) - if ("version" in raw && raw.version === 2) return raw as Writable + if (raw && typeof raw === "object") { + if ("version" in raw && raw.version === 2) return raw as Writable + return yield* writeMigrated(raw as Record) + } - const migrated = migrate(raw as Record) - yield* fsys - .writeJson(file, migrated, 0o600) - .pipe(Effect.mapError((cause) => new AuthFileWriteError({ operation: "migrate", cause }))) - return migrated + return { version: 2, accounts: {}, active: {} } }) const write = (data: Writable) => diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts new file mode 100644 index 000000000..3aa591542 --- /dev/null +++ b/packages/core/src/catalog.ts @@ -0,0 +1,258 @@ +export * as Catalog from "./catalog" + +import { Context, Effect, HashMap, Layer, Option, Order, pipe, Schema, Array } from "effect" +import { produce, type Draft } from "immer" +import { ModelV2 } from "./model" +import { PluginV2 } from "./plugin" +import { ProviderV2 } from "./provider" + +type ProviderRecord = { + provider: ProviderV2.Info + models: HashMap.HashMap +} + +export class ProviderNotFoundError extends Schema.TaggedErrorClass()( + "CatalogV2.ProviderNotFound", + { + providerID: ProviderV2.ID, + }, +) {} + +export class ModelNotFoundError extends Schema.TaggedErrorClass()("CatalogV2.ModelNotFound", { + providerID: ProviderV2.ID, + modelID: ModelV2.ID, +}) {} + +export interface Interface { + readonly provider: { + readonly get: (providerID: ProviderV2.ID) => Effect.Effect + readonly update: (providerID: ProviderV2.ID, fn: (provider: Draft) => void) => Effect.Effect + readonly all: () => Effect.Effect + readonly available: () => Effect.Effect + } + readonly model: { + readonly get: ( + providerID: ProviderV2.ID, + modelID: ModelV2.ID, + ) => Effect.Effect + readonly update: ( + providerID: ProviderV2.ID, + modelID: ModelV2.ID, + fn: (model: Draft) => void, + ) => Effect.Effect + readonly all: () => Effect.Effect + readonly available: () => Effect.Effect + readonly default: () => Effect.Effect> + readonly setDefault: ( + providerID: ProviderV2.ID, + modelID: ModelV2.ID, + ) => Effect.Effect + readonly small: (providerID: ProviderV2.ID) => Effect.Effect> + } +} + +export class Service extends Context.Service()("@opencode/v2/Catalog") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + let records = HashMap.empty() + let defaultModel: { providerID: ProviderV2.ID; modelID: ModelV2.ID } | undefined + const plugin = yield* PluginV2.Service + + const resolve = (model: ModelV2.Info) => { + const provider = Option.getOrThrow(HashMap.get(records, model.providerID)).provider + const endpoint = + model.endpoint.type === "unknown" + ? provider.endpoint + : model.endpoint.type === "aisdk" && provider.endpoint.type === "aisdk" && !model.endpoint.url + ? { ...model.endpoint, url: provider.endpoint.url } + : model.endpoint + const options = { + headers: { + ...provider.options.headers, + ...model.options.headers, + }, + body: { + ...provider.options.body, + ...model.options.body, + }, + aisdk: { + provider: { + ...provider.options.aisdk.provider, + ...model.options.aisdk.provider, + }, + request: model.options.aisdk.request, + }, + variant: model.options.variant, + } + return new ModelV2.Info({ + ...model, + endpoint, + options, + }) + } + + function* getRecord(providerID: ProviderV2.ID) { + const match = HashMap.get(records, providerID) + if (!match.valueOrUndefined) return yield* new ProviderNotFoundError({ providerID }) + return match.value + } + + const result: Interface = { + provider: { + get: Effect.fn("CatalogV2.provider.get")(function* (providerID) { + const record = yield* getRecord(providerID) + return record.provider + }), + + update: Effect.fnUntraced(function* (providerID, fn) { + const current = Option.getOrUndefined(HashMap.get(records, providerID)) + const provider = produce(current?.provider ?? ProviderV2.Info.empty(providerID), (draft) => { + fn(draft) + if (draft.endpoint.type === "aisdk" && typeof draft.options.aisdk.provider.baseURL === "string") { + draft.endpoint.url = draft.options.aisdk.provider.baseURL + delete draft.options.aisdk.provider.baseURL + } + }) + const updated = yield* plugin.trigger("provider.update", {}, { provider, cancel: false }) + records = HashMap.set(records, providerID, { + provider: updated.provider, + models: current?.models ?? HashMap.empty(), + }) + }), + + all: Effect.fn("CatalogV2.provider.all")(function* () { + return globalThis.Array.from(HashMap.values(records)).map((record) => record.provider) + }), + + available: Effect.fn("CatalogV2.provider.available")(function* () { + return globalThis.Array.from(HashMap.values(records)) + .map((record) => record.provider) + .filter((provider) => provider.enabled) + }), + }, + + model: { + get: Effect.fn("CatalogV2.model.get")(function* (providerID, modelID) { + const record = yield* getRecord(providerID) + const model = Option.getOrUndefined(HashMap.get(record.models, modelID)) + if (!model) return yield* new ModelNotFoundError({ providerID, modelID }) + return resolve(model) + }), + + update: Effect.fnUntraced(function* (providerID, modelID, fn) { + const record = yield* getRecord(providerID) + const model = produce( + HashMap.get(record.models, modelID).pipe(Option.getOrElse(() => ModelV2.Info.empty(providerID, modelID))), + (draft) => { + fn(draft) + if (draft.endpoint.type === "aisdk" && typeof draft.options.aisdk.provider.baseURL === "string") { + draft.endpoint.url = draft.options.aisdk.provider.baseURL + delete draft.options.aisdk.provider.baseURL + } + }, + ) + const updated = yield* plugin.trigger("model.update", {}, { model, cancel: false }) + if (updated.cancel) return + records = HashMap.set(records, providerID, { + provider: record.provider, + models: HashMap.set( + record.models, + modelID, + new ModelV2.Info({ ...updated.model, id: modelID, providerID }), + ), + }) + return + }), + + all: Effect.fn("CatalogV2.model.all")(function* () { + return pipe( + records, + HashMap.toValues, + Array.flatMap((record) => HashMap.toValues(record.models)), + Array.map(resolve), + Array.sortWith((item) => item.time.released.epochMilliseconds, Order.flip(Order.Number)), + ) + }), + + available: Effect.fn("CatalogV2.model.available")(function* () { + return (yield* result.model.all()).filter((model) => { + const record = Option.getOrUndefined(HashMap.get(records, model.providerID)) + return record?.provider.enabled !== false && model.enabled + }) + }), + + default: Effect.fn("CatalogV2.model.default")(function* () { + if (defaultModel) { + const model = yield* result.model.get(defaultModel.providerID, defaultModel.modelID).pipe(Effect.option) + if (Option.isSome(model) && model.value.enabled) return model + } + + return pipe( + yield* result.model.available(), + Array.sortWith((item) => item.time.released.epochMilliseconds, Order.flip(Order.Number)), + Array.head, + ) + }), + + setDefault: Effect.fn("CatalogV2.model.setDefault")(function* (providerID, modelID) { + yield* result.model.get(providerID, modelID) + defaultModel = { providerID, modelID } + }), + + small: Effect.fn("CatalogV2.model.small")(function* (providerID) { + const record = Option.getOrUndefined(HashMap.get(records, providerID)) + if (!record) return Option.none() + + if (providerID === ProviderV2.ID.opencode) { + const gpt5Nano = Option.getOrUndefined(HashMap.get(record.models, ModelV2.ID.make("gpt-5-nano"))) + if (gpt5Nano?.enabled && gpt5Nano.status === "active") return Option.some(resolve(gpt5Nano)) + } + + const candidates = pipe( + HashMap.toValues(record.models), + Array.filter( + (model) => + model.providerID === providerID && + model.enabled && + model.status === "active" && + model.capabilities.input.some((item) => item.startsWith("text")) && + model.capabilities.output.some((item) => item.startsWith("text")), + ), + Array.map((model) => ({ + model, + cost: model.cost[0] ? model.cost[0].input + model.cost[0].output : 999, + age: (Date.now() - model.time.released.epochMilliseconds) / (1000 * 60 * 60 * 24 * 30), + small: SMALL_MODEL_RE.test(`${model.id} ${model.family ?? ""} ${model.name}`.toLowerCase()), + })), + Array.filter((item) => item.cost > 0 && item.age <= 18), + ) + + const pick = (items: typeof candidates) => { + const maxCost = Math.max(...items.map((item) => item.cost), 0.01) + const maxAge = Math.max(...items.map((item) => item.age), 0.01) + return pipe( + items, + Array.sortWith((item) => (item.cost / maxCost) * 0.8 + (item.age / maxAge) * 0.2, Order.Number), + Array.map((item) => resolve(item.model)), + Array.head, + ) + } + + return pipe( + candidates, + Array.filter((item) => item.small), + (items) => (items.length > 0 ? pick(items) : pick(candidates)), + ) + }), + }, + } + + return Service.of(result) + }), +) + +const SMALL_MODEL_RE = /\b(nano|flash|lite|mini|haiku|small|fast)\b/ + +export const defaultLayer = layer.pipe(Layer.provide(PluginV2.defaultLayer)) diff --git a/packages/opencode/src/provider/sdk/copilot/README.md b/packages/core/src/github-copilot/README.md similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/README.md rename to packages/core/src/github-copilot/README.md diff --git a/packages/opencode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts b/packages/core/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts rename to packages/core/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts diff --git a/packages/opencode/src/provider/sdk/copilot/chat/get-response-metadata.ts b/packages/core/src/github-copilot/chat/get-response-metadata.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/chat/get-response-metadata.ts rename to packages/core/src/github-copilot/chat/get-response-metadata.ts diff --git a/packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts b/packages/core/src/github-copilot/chat/map-openai-compatible-finish-reason.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts rename to packages/core/src/github-copilot/chat/map-openai-compatible-finish-reason.ts diff --git a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts b/packages/core/src/github-copilot/chat/openai-compatible-api-types.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts rename to packages/core/src/github-copilot/chat/openai-compatible-api-types.ts diff --git a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts b/packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts rename to packages/core/src/github-copilot/chat/openai-compatible-chat-language-model.ts diff --git a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts b/packages/core/src/github-copilot/chat/openai-compatible-chat-options.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts rename to packages/core/src/github-copilot/chat/openai-compatible-chat-options.ts diff --git a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts b/packages/core/src/github-copilot/chat/openai-compatible-metadata-extractor.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts rename to packages/core/src/github-copilot/chat/openai-compatible-metadata-extractor.ts diff --git a/packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts b/packages/core/src/github-copilot/chat/openai-compatible-prepare-tools.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts rename to packages/core/src/github-copilot/chat/openai-compatible-prepare-tools.ts diff --git a/packages/opencode/src/provider/sdk/copilot/copilot-provider.ts b/packages/core/src/github-copilot/copilot-provider.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/copilot-provider.ts rename to packages/core/src/github-copilot/copilot-provider.ts diff --git a/packages/opencode/src/provider/sdk/copilot/openai-compatible-error.ts b/packages/core/src/github-copilot/openai-compatible-error.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/openai-compatible-error.ts rename to packages/core/src/github-copilot/openai-compatible-error.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts b/packages/core/src/github-copilot/responses/convert-to-openai-responses-input.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts rename to packages/core/src/github-copilot/responses/convert-to-openai-responses-input.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts b/packages/core/src/github-copilot/responses/map-openai-responses-finish-reason.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts rename to packages/core/src/github-copilot/responses/map-openai-responses-finish-reason.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/openai-config.ts b/packages/core/src/github-copilot/responses/openai-config.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/openai-config.ts rename to packages/core/src/github-copilot/responses/openai-config.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/openai-error.ts b/packages/core/src/github-copilot/responses/openai-error.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/openai-error.ts rename to packages/core/src/github-copilot/responses/openai-error.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-api-types.ts b/packages/core/src/github-copilot/responses/openai-responses-api-types.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/openai-responses-api-types.ts rename to packages/core/src/github-copilot/responses/openai-responses-api-types.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts b/packages/core/src/github-copilot/responses/openai-responses-language-model.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts rename to packages/core/src/github-copilot/responses/openai-responses-language-model.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts b/packages/core/src/github-copilot/responses/openai-responses-prepare-tools.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts rename to packages/core/src/github-copilot/responses/openai-responses-prepare-tools.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/openai-responses-settings.ts b/packages/core/src/github-copilot/responses/openai-responses-settings.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/openai-responses-settings.ts rename to packages/core/src/github-copilot/responses/openai-responses-settings.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/code-interpreter.ts b/packages/core/src/github-copilot/responses/tool/code-interpreter.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/tool/code-interpreter.ts rename to packages/core/src/github-copilot/responses/tool/code-interpreter.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/file-search.ts b/packages/core/src/github-copilot/responses/tool/file-search.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/tool/file-search.ts rename to packages/core/src/github-copilot/responses/tool/file-search.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/image-generation.ts b/packages/core/src/github-copilot/responses/tool/image-generation.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/tool/image-generation.ts rename to packages/core/src/github-copilot/responses/tool/image-generation.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/local-shell.ts b/packages/core/src/github-copilot/responses/tool/local-shell.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/tool/local-shell.ts rename to packages/core/src/github-copilot/responses/tool/local-shell.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search-preview.ts b/packages/core/src/github-copilot/responses/tool/web-search-preview.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/tool/web-search-preview.ts rename to packages/core/src/github-copilot/responses/tool/web-search-preview.ts diff --git a/packages/opencode/src/provider/sdk/copilot/responses/tool/web-search.ts b/packages/core/src/github-copilot/responses/tool/web-search.ts similarity index 100% rename from packages/opencode/src/provider/sdk/copilot/responses/tool/web-search.ts rename to packages/core/src/github-copilot/responses/tool/web-search.ts diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts new file mode 100644 index 000000000..77b8c60eb --- /dev/null +++ b/packages/core/src/model.ts @@ -0,0 +1,116 @@ +import { DateTime, Schema } from "effect" +import { DateTimeUtcFromMillis } from "effect/Schema" +import { ProviderV2 } from "./provider" + +export const ID = Schema.String.pipe(Schema.brand("ModelV2.ID")) +export type ID = typeof ID.Type + +export const VariantID = Schema.String.pipe(Schema.brand("VariantID")) +export type VariantID = typeof VariantID.Type + +// Grouping of models, eg claude opus, claude sonnet +export const Family = Schema.String.pipe(Schema.brand("Family")) +export type Family = typeof Family.Type + +export const Capabilities = Schema.Struct({ + tools: Schema.Boolean, + // mime patterns, image, audio, video/*, text/* + input: Schema.String.pipe(Schema.Array), + output: Schema.String.pipe(Schema.Array), +}) +export type Capabilities = typeof Capabilities.Type + +export const Cost = Schema.Struct({ + tier: Schema.Struct({ + type: Schema.Literal("context"), + size: Schema.Int, + }).pipe(Schema.optional), + input: Schema.Finite, + output: Schema.Finite, + cache: Schema.Struct({ + read: Schema.Finite, + write: Schema.Finite, + }), +}) + +export const Ref = Schema.Struct({ + id: ID, + providerID: ProviderV2.ID, + variant: VariantID, +}) +export type Ref = typeof Ref.Type + +export class Info extends Schema.Class("ModelV2.Info")({ + id: ID, + apiID: ID, + providerID: ProviderV2.ID, + family: Family.pipe(Schema.optional), + name: Schema.String, + endpoint: ProviderV2.Endpoint, + capabilities: Capabilities, + options: Schema.Struct({ + ...ProviderV2.Options.fields, + variant: Schema.String.pipe(Schema.optional), + }), + variants: Schema.Struct({ + id: VariantID, + ...ProviderV2.Options.fields, + }).pipe(Schema.Array), + time: Schema.Struct({ + released: DateTimeUtcFromMillis, + }), + cost: Cost.pipe(Schema.Array), + status: Schema.Literals(["alpha", "beta", "deprecated", "active"]), + enabled: Schema.Boolean, + limit: Schema.Struct({ + context: Schema.Int, + input: Schema.Int.pipe(Schema.optional), + output: Schema.Int, + }), +}) { + static empty(providerID: ProviderV2.ID, modelID: ID) { + return new Info({ + id: modelID, + apiID: modelID, + providerID, + name: modelID, + endpoint: { + type: "unknown", + }, + capabilities: { + tools: false, + input: [], + output: [], + }, + options: { + headers: {}, + body: {}, + aisdk: { + provider: {}, + request: {}, + }, + }, + variants: [], + time: { + released: DateTime.makeUnsafe(0), + }, + cost: [], + status: "active", + enabled: true, + limit: { + context: 0, + output: 0, + }, + }) + } +} + +export function parse(input: string): { providerID: ProviderV2.ID; modelID: ID } { + const [providerID, ...modelID] = input.split("/") + return { + providerID: ProviderV2.ID.make(providerID), + modelID: ID.make(modelID.join("/")), + } +} + +export * as ModelV2 from "./model" diff --git a/packages/core/src/plugin.ts b/packages/core/src/plugin.ts new file mode 100644 index 000000000..dfcae9468 --- /dev/null +++ b/packages/core/src/plugin.ts @@ -0,0 +1,146 @@ +export * as PluginV2 from "./plugin" + +import { createDraft, finishDraft, type Draft } from "immer" +import type { LanguageModelV3 } from "@ai-sdk/provider" +import { type ProviderV2 } from "./provider" +import { Context, Effect, Layer, Schema } from "effect" +import type { ModelV2 } from "./model" + +export const ID = Schema.String.pipe(Schema.brand("Plugin.ID")) +export type ID = typeof ID.Type + +type HookSpec = { + "provider.update": { + input: {} + output: { + provider: ProviderV2.Info + cancel: boolean + } + } + "model.update": { + input: {} + output: { + model: ModelV2.Info + cancel: boolean + } + } + "aisdk.language": { + input: { + model: ModelV2.Info + sdk: any + options: Record + } + output: { + language?: LanguageModelV3 + } + } + "aisdk.sdk": { + input: { + model: ModelV2.Info + package: string + options: Record + } + output: { + sdk?: any + } + } +} + +export type Hooks = { + [Name in keyof HookSpec]: Readonly & { + -readonly [Field in keyof HookSpec[Name]["output"]]: HookSpec[Name]["output"][Field] extends object + ? Draft + : HookSpec[Name]["output"][Field] + } +} + +export type HookFunctions = { + [key in keyof Hooks]?: (input: Hooks[key]) => Effect.Effect +} + +export type HookInput = HookSpec[Name]["input"] +export type HookOutput = HookSpec[Name]["output"] + +export type Effect = Effect.Effect + +export function define(input: { id: ID; effect: Effect.Effect }) { + return input +} + +export interface Interface { + readonly add: (input: { id: ID; effect: Effect }) => Effect.Effect + readonly remove: (id: ID) => Effect.Effect + readonly trigger: ( + name: Name, + input: HookInput, + output: HookOutput, + ) => Effect.Effect & HookOutput> +} + +export class Service extends Context.Service()("@opencode/v2/Plugin") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + let hooks: { + id: ID + hooks: HookFunctions + }[] = [] + + const svc = Service.of({ + add: Effect.fn("Plugin.add")(function* (input) { + const result = yield* input.effect + if (!result) return + hooks = [ + ...hooks.filter((item) => item.id !== input.id), + { + id: input.id, + hooks: result, + }, + ] + }), + trigger: Effect.fn("Plugin.trigger")(function* (name, input, output) { + const draftEntries = new Map>() + const event = { + ...input, + ...output, + } as Record + + for (const [field, value] of Object.entries(output)) { + if (value && typeof value === "object") { + draftEntries.set(field, createDraft(value)) + event[field] = draftEntries.get(field) + } + } + + for (const item of hooks) { + const match = item.hooks[name] + if (!match) continue + yield* match(event as any).pipe( + Effect.withSpan(`Plugin.hook.${name}`, { + attributes: { + plugin: item.id, + hook: name, + }, + }), + ) + } + + for (const [field, draft] of draftEntries) { + event[field] = finishDraft(draft) + } + + return event as any + }), + remove: Effect.fn("Plugin.remove")(function* (id) { + hooks = hooks.filter((item) => item.id !== id) + }), + }) + return svc + }), +) + +export const defaultLayer = layer + +// opencode +// sdcok diff --git a/packages/core/src/plugin/auth.ts b/packages/core/src/plugin/auth.ts new file mode 100644 index 000000000..81cbfbe3f --- /dev/null +++ b/packages/core/src/plugin/auth.ts @@ -0,0 +1,27 @@ +import { Effect } from "effect" +import { AuthV2 } from "../auth" +import { PluginV2 } from "../plugin" + +export const AuthPlugin = PluginV2.define({ + id: PluginV2.ID.make("auth"), + effect: Effect.gen(function* () { + const auth = yield* AuthV2.Service + return { + "provider.update": Effect.fn(function* (evt) { + const account = yield* auth.active(AuthV2.ServiceID.make(evt.provider.id)).pipe(Effect.orDie) + if (!account) return + evt.provider.enabled = { + via: "auth", + service: account.serviceID, + } + if (account.credential.type === "api") { + evt.provider.options.aisdk.provider.apiKey = account.credential.key + Object.assign(evt.provider.options.aisdk.provider, account.credential.metadata ?? {}) + } + if (account.credential.type === "oauth") { + evt.provider.options.aisdk.provider.apiKey = account.credential.access + } + }), + } + }), +}) diff --git a/packages/core/src/plugin/env.ts b/packages/core/src/plugin/env.ts new file mode 100644 index 000000000..d63936fa1 --- /dev/null +++ b/packages/core/src/plugin/env.ts @@ -0,0 +1,18 @@ +import { Effect } from "effect" +import { PluginV2 } from "../plugin" + +export const EnvPlugin = PluginV2.define({ + id: PluginV2.ID.make("env"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + const key = evt.provider.env.find((item) => process.env[item]) + if (!key) return + evt.provider.enabled = { + via: "env", + name: key, + } + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider.ts b/packages/core/src/plugin/provider.ts new file mode 100644 index 000000000..188078749 --- /dev/null +++ b/packages/core/src/plugin/provider.ts @@ -0,0 +1 @@ +export { ProviderPlugins } from "./provider/index" diff --git a/packages/core/src/plugin/provider/alibaba.ts b/packages/core/src/plugin/provider/alibaba.ts new file mode 100644 index 000000000..fa5c0a91c --- /dev/null +++ b/packages/core/src/plugin/provider/alibaba.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const AlibabaPlugin = PluginV2.define({ + id: PluginV2.ID.make("alibaba"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/alibaba") return + const mod = yield* Effect.promise(() => import("@ai-sdk/alibaba")) + evt.sdk = mod.createAlibaba(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/amazon-bedrock.ts b/packages/core/src/plugin/provider/amazon-bedrock.ts new file mode 100644 index 000000000..366548a0a --- /dev/null +++ b/packages/core/src/plugin/provider/amazon-bedrock.ts @@ -0,0 +1,94 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +// Bedrock cross-region inference profiles require regional prefixes only for +// specific model/region combinations. Keep the mapping narrow and avoid +// double-prefixing model IDs that models.dev already marks as global/us/eu/etc. +function resolveModelID(modelID: string, region: string | undefined) { + const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."] + if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) return modelID + + const resolvedRegion = region ?? "us-east-1" + const regionPrefix = resolvedRegion.split("-")[0] + if (regionPrefix === "us") { + const requiresPrefix = ["nova-micro", "nova-lite", "nova-pro", "nova-premier", "nova-2", "claude", "deepseek"].some( + (item) => modelID.includes(item), + ) + if (requiresPrefix && !resolvedRegion.startsWith("us-gov")) return `${regionPrefix}.${modelID}` + return modelID + } + if (regionPrefix === "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((item) => resolvedRegion.includes(item)) + const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((item) => + modelID.includes(item), + ) + return regionRequiresPrefix && modelRequiresPrefix ? `${regionPrefix}.${modelID}` : modelID + } + if (regionPrefix !== "ap") return modelID + + const australia = ["ap-southeast-2", "ap-southeast-4"].includes(resolvedRegion) + if (australia && ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((item) => modelID.includes(item))) { + return `au.${modelID}` + } + + const prefix = resolvedRegion === "ap-northeast-1" ? "jp" : "apac" + return ["claude", "nova-lite", "nova-micro", "nova-pro"].some((item) => modelID.includes(item)) + ? `${prefix}.${modelID}` + : modelID +} + +export const AmazonBedrockPlugin = PluginV2.define({ + id: PluginV2.ID.make("amazon-bedrock"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.amazonBedrock) return + if (evt.provider.endpoint.type !== "aisdk") return + if (typeof evt.provider.options.aisdk.provider.endpoint !== "string") return + // The AI SDK expects a base URL, but users configure Bedrock private/VPC + // endpoints as `endpoint`; move it into the catalog endpoint URL once. + evt.provider.endpoint.url = evt.provider.options.aisdk.provider.endpoint + delete evt.provider.options.aisdk.provider.endpoint + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/amazon-bedrock") return + const options = { ...evt.options } + const profile = typeof options.profile === "string" ? options.profile : process.env.AWS_PROFILE + const region = typeof options.region === "string" ? options.region : (process.env.AWS_REGION ?? "us-east-1") + const bearerToken = + process.env.AWS_BEARER_TOKEN_BEDROCK ?? + (typeof options.bearerToken === "string" ? options.bearerToken : undefined) + if (bearerToken && !process.env.AWS_BEARER_TOKEN_BEDROCK) process.env.AWS_BEARER_TOKEN_BEDROCK = bearerToken + const containerCreds = Boolean( + process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI, + ) + + options.region = region + if (typeof options.endpoint === "string") options.baseURL = options.endpoint + if (!bearerToken && options.credentialProvider === undefined) { + // Do not gate SDK creation on explicit AWS env vars. The default chain + // also handles ~/.aws/credentials, SSO, process creds, and instance roles. + const { fromNodeProviderChain } = yield* Effect.promise(() => import("@aws-sdk/credential-providers")) + options.credentialProvider = fromNodeProviderChain(profile ? { profile } : {}) + } + + const mod = yield* Effect.promise(() => import("@ai-sdk/amazon-bedrock")) + evt.sdk = mod.createAmazonBedrock(options) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return + const region = typeof evt.options.region === "string" ? evt.options.region : process.env.AWS_REGION + evt.language = evt.sdk.languageModel(resolveModelID(evt.model.apiID, region)) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/anthropic.ts b/packages/core/src/plugin/provider/anthropic.ts new file mode 100644 index 000000000..14851c4a3 --- /dev/null +++ b/packages/core/src/plugin/provider/anthropic.ts @@ -0,0 +1,21 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const AnthropicPlugin = PluginV2.define({ + id: PluginV2.ID.make("anthropic"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.anthropic) return + evt.provider.options.headers["anthropic-beta"] = + "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14" + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/anthropic") return + const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic")) + evt.sdk = mod.createAnthropic(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts new file mode 100644 index 000000000..86c3eb924 --- /dev/null +++ b/packages/core/src/plugin/provider/azure.ts @@ -0,0 +1,67 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +function selectLanguage(sdk: any, modelID: string, useChat: boolean) { + if (useChat && sdk.chat) return sdk.chat(modelID) + if (sdk.responses) return sdk.responses(modelID) + if (sdk.messages) return sdk.messages(modelID) + if (sdk.chat) return sdk.chat(modelID) + return sdk.languageModel(modelID) +} + +export const AzurePlugin = PluginV2.define({ + id: PluginV2.ID.make("azure"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.azure) return + const configured = evt.provider.options.aisdk.provider.resourceName + const resourceName = + typeof configured === "string" && configured.trim() !== "" ? configured : process.env.AZURE_RESOURCE_NAME + if (resourceName) evt.provider.options.aisdk.provider.resourceName = resourceName + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/azure") return + if (evt.model.providerID === ProviderV2.ID.azure) { + if (!evt.options.resourceName && !evt.options.baseURL && (evt.model.endpoint.type !== "aisdk" || !evt.model.endpoint.url)) { + throw new Error( + "AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it", + ) + } + } + const mod = yield* Effect.promise(() => import("@ai-sdk/azure")) + evt.sdk = mod.createAzure(evt.options) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.azure) return + evt.language = selectLanguage( + evt.sdk, + evt.model.apiID, + Boolean(evt.options.useCompletionUrls), + ) + }), + } + }), +}) + +export const AzureCognitiveServicesPlugin = PluginV2.define({ + id: PluginV2.ID.make("azure-cognitive-services"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.make("azure-cognitive-services")) return + const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME + if (resourceName) evt.provider.options.aisdk.provider.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai` + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return + evt.language = selectLanguage( + evt.sdk, + evt.model.apiID, + Boolean(evt.options.useCompletionUrls), + ) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/cerebras.ts b/packages/core/src/plugin/provider/cerebras.ts new file mode 100644 index 000000000..b2fadd8bf --- /dev/null +++ b/packages/core/src/plugin/provider/cerebras.ts @@ -0,0 +1,20 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const CerebrasPlugin = PluginV2.define({ + id: PluginV2.ID.make("cerebras"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.make("cerebras")) return + evt.provider.options.headers["X-Cerebras-3rd-Party-Integration"] = "opencode" + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/cerebras") return + const mod = yield* Effect.promise(() => import("@ai-sdk/cerebras")) + evt.sdk = mod.createCerebras(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts b/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts new file mode 100644 index 000000000..ffcd4adcf --- /dev/null +++ b/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts @@ -0,0 +1,81 @@ +import os from "os" +import { InstallationVersion } from "../../installation/version" +import { Effect, Option, Schema } from "effect" +import { PluginV2 } from "../../plugin" + +export const CloudflareAIGatewayPlugin = PluginV2.define({ + id: PluginV2.ID.make("cloudflare-ai-gateway"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "ai-gateway-provider") return + if (evt.options.baseURL) return + + const config = gatewayConfig(evt.options) + if (!config) return + const metadata = gatewayMetadata(evt.options) + const { createAiGateway } = yield* Effect.promise(() => import("ai-gateway-provider")).pipe(Effect.orDie) + const { createUnified } = yield* Effect.promise(() => import("ai-gateway-provider/providers/unified")).pipe( + Effect.orDie, + ) + const gateway = createAiGateway({ + accountId: config.accountId, + gateway: config.gatewayId, + apiKey: config.apiKey, + options: gatewayOptions(evt.options, metadata), + } as any) + const unified = createUnified() + evt.sdk = { + languageModel(modelID: string) { + return gateway(unified(modelID)) + }, + } + }), + } + }), +}) + +type GatewayConfig = { + accountId: string + gatewayId: string + apiKey: string +} + +const decodeJson = Schema.decodeUnknownOption(Schema.UnknownFromJsonString) + +function gatewayConfig(options: Record): GatewayConfig | undefined { + const accountId = process.env.CLOUDFLARE_ACCOUNT_ID ?? stringOption(options, "accountId") + // AuthPlugin copies CLI prompt metadata into options. The prompt stores the + // gateway as gatewayId, while older config examples may use gateway. + const gatewayId = + process.env.CLOUDFLARE_GATEWAY_ID ?? stringOption(options, "gatewayId") ?? stringOption(options, "gateway") + const apiKey = process.env.CLOUDFLARE_API_TOKEN ?? process.env.CF_AIG_TOKEN ?? stringOption(options, "apiKey") + if (!accountId || !gatewayId || !apiKey) return undefined + + return { accountId, gatewayId, apiKey } +} + +function gatewayMetadata(options: Record) { + // Preserve the legacy cf-aig-metadata header escape hatch for gateway logging + // metadata, but prefer the typed metadata option when present. + if (options.metadata !== undefined) return options.metadata + const raw = (options.headers as Record | undefined)?.["cf-aig-metadata"] + return raw ? Option.getOrUndefined(decodeJson(raw)) : undefined +} + +function gatewayOptions(options: Record, metadata: unknown) { + return { + metadata, + cacheTtl: options.cacheTtl, + cacheKey: options.cacheKey, + skipCache: options.skipCache, + collectLog: options.collectLog, + headers: { + "User-Agent": `opencode/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`, + }, + } +} + +function stringOption(options: Record, key: string) { + return typeof options[key] === "string" ? options[key] : undefined +} diff --git a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts new file mode 100644 index 000000000..f39869b57 --- /dev/null +++ b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts @@ -0,0 +1,69 @@ +import os from "os" +import { InstallationVersion } from "../../installation/version" +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +const providerID = ProviderV2.ID.make("cloudflare-workers-ai") + +export const CloudflareWorkersAIPlugin = PluginV2.define({ + id: PluginV2.ID.make("cloudflare-workers-ai"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== providerID) return + if (evt.provider.endpoint.type !== "aisdk") return + if (evt.provider.endpoint.url) return + + const accountId = resolveAccountId(evt.provider.options.aisdk.provider) + if (accountId) evt.provider.endpoint.url = workersEndpoint(accountId) + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.model.providerID !== providerID) return + if (evt.package !== "@ai-sdk/openai-compatible") return + + if (!hasWorkersEndpoint(evt.model.endpoint)) return + const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible")) + evt.sdk = mod.createOpenAICompatible(sdkOptions(evt.options) as any) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== providerID) return + evt.language = evt.sdk.languageModel(evt.model.apiID) + }), + } + }), +}) + +function resolveAccountId(options: Record) { + return process.env.CLOUDFLARE_ACCOUNT_ID ?? stringOption(options, "accountId") +} + +function workersEndpoint(accountId: string) { + return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1` +} + +function hasWorkersEndpoint(endpoint: ProviderV2.Endpoint) { + return endpoint.type === "aisdk" && Boolean(endpoint.url) +} + +function sdkOptions(options: Record) { + return { + ...options, + baseURL: expandAccountId(options.baseURL), + apiKey: process.env.CLOUDFLARE_API_KEY ?? options.apiKey, + headers: { + "User-Agent": `opencode/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`, + ...options.headers, + }, + name: providerID, + } +} + +function expandAccountId(baseURL: unknown) { + if (typeof baseURL !== "string") return baseURL + return baseURL.replaceAll("${CLOUDFLARE_ACCOUNT_ID}", process.env.CLOUDFLARE_ACCOUNT_ID ?? "${CLOUDFLARE_ACCOUNT_ID}") +} + +function stringOption(options: Record, key: string) { + return typeof options[key] === "string" ? options[key] : undefined +} diff --git a/packages/core/src/plugin/provider/cohere.ts b/packages/core/src/plugin/provider/cohere.ts new file mode 100644 index 000000000..991c370d1 --- /dev/null +++ b/packages/core/src/plugin/provider/cohere.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const CoherePlugin = PluginV2.define({ + id: PluginV2.ID.make("cohere"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/cohere") return + const mod = yield* Effect.promise(() => import("@ai-sdk/cohere")) + evt.sdk = mod.createCohere(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/deepinfra.ts b/packages/core/src/plugin/provider/deepinfra.ts new file mode 100644 index 000000000..bbd42f6e2 --- /dev/null +++ b/packages/core/src/plugin/provider/deepinfra.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const DeepInfraPlugin = PluginV2.define({ + id: PluginV2.ID.make("deepinfra"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/deepinfra") return + const mod = yield* Effect.promise(() => import("@ai-sdk/deepinfra")) + evt.sdk = mod.createDeepInfra(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/dynamic.ts b/packages/core/src/plugin/provider/dynamic.ts new file mode 100644 index 000000000..e5abc7009 --- /dev/null +++ b/packages/core/src/plugin/provider/dynamic.ts @@ -0,0 +1,31 @@ +import { Npm } from "../../npm" +import { Effect, Option } from "effect" +import { pathToFileURL } from "url" +import { PluginV2 } from "../../plugin" + +export const DynamicProviderPlugin = PluginV2.define({ + id: PluginV2.ID.make("dynamic-provider"), + effect: Effect.gen(function* () { + const npm = yield* Npm.Service + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.sdk) return + + const installedPath = evt.package.startsWith("file://") + ? evt.package + : Option.getOrUndefined((yield* npm.add(evt.package).pipe(Effect.orDie)).entrypoint) + if (!installedPath) throw new Error(`Package ${evt.package} has no import entrypoint`) + + const mod = yield* Effect.promise(async () => { + return (await import( + installedPath.startsWith("file://") ? installedPath : pathToFileURL(installedPath).href + )) as Record any> + }).pipe(Effect.orDie) + const match = Object.keys(mod).find((name) => name.startsWith("create")) + if (!match) throw new Error(`Package ${evt.package} has no provider factory export`) + + evt.sdk = mod[match](evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/gateway.ts b/packages/core/src/plugin/provider/gateway.ts new file mode 100644 index 000000000..5b08ad9ef --- /dev/null +++ b/packages/core/src/plugin/provider/gateway.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const GatewayPlugin = PluginV2.define({ + id: PluginV2.ID.make("gateway"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/gateway") return + const mod = yield* Effect.promise(() => import("@ai-sdk/gateway")) + evt.sdk = mod.createGateway(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/github-copilot.ts b/packages/core/src/plugin/provider/github-copilot.ts new file mode 100644 index 000000000..31e57ba12 --- /dev/null +++ b/packages/core/src/plugin/provider/github-copilot.ts @@ -0,0 +1,44 @@ +import { Effect } from "effect" +import { ModelV2 } from "../../model" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +function shouldUseResponses(modelID: string) { + // Copilot supports Responses for GPT-5 class models, except mini variants + // which still need the chat-completions endpoint. + const match = /^gpt-(\d+)/.exec(modelID) + if (!match) return false + return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini") +} + +export const GithubCopilotPlugin = PluginV2.define({ + id: PluginV2.ID.make("github-copilot"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.githubCopilot) return + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/github-copilot") return + const mod = yield* Effect.promise(() => import("../../github-copilot/copilot-provider")) + evt.sdk = mod.createOpenaiCompatible(evt.options) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return + if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) { + evt.language = evt.sdk.languageModel(evt.model.apiID) + return + } + evt.language = shouldUseResponses(evt.model.apiID) + ? evt.sdk.responses(evt.model.apiID) + : evt.sdk.chat(evt.model.apiID) + }), + "model.update": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return + // This chat-only alias conflicts with the Copilot GPT-5 Responses route, + // so hide it only for Copilot rather than for every provider catalog. + if (evt.model.id === ModelV2.ID.make("gpt-5-chat-latest")) evt.cancel = true + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/gitlab.ts b/packages/core/src/plugin/provider/gitlab.ts new file mode 100644 index 000000000..be923e7cb --- /dev/null +++ b/packages/core/src/plugin/provider/gitlab.ts @@ -0,0 +1,64 @@ +import os from "os" +import { InstallationVersion } from "../../installation/version" +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const GitLabPlugin = PluginV2.define({ + id: PluginV2.ID.make("gitlab"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "gitlab-ai-provider") return + const mod = yield* Effect.promise(() => import("gitlab-ai-provider")) + evt.sdk = mod.createGitLab({ + ...evt.options, + instanceUrl: + typeof evt.options.instanceUrl === "string" + ? evt.options.instanceUrl + : (process.env.GITLAB_INSTANCE_URL ?? "https://gitlab.com"), + apiKey: typeof evt.options.apiKey === "string" ? evt.options.apiKey : process.env.GITLAB_TOKEN, + aiGatewayHeaders: { + "User-Agent": `opencode/${InstallationVersion} gitlab-ai-provider/${mod.VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`, + "anthropic-beta": "context-1m-2025-08-07", + ...evt.options.aiGatewayHeaders, + }, + featureFlags: { + duo_agent_platform_agentic_chat: true, + duo_agent_platform: true, + ...evt.options.featureFlags, + }, + }) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.gitlab) return + const featureFlags = typeof evt.options.featureFlags === "object" && evt.options.featureFlags ? evt.options.featureFlags : {} + if (evt.model.apiID.startsWith("duo-workflow-")) { + const gitlab = yield* Effect.promise(() => import("gitlab-ai-provider")).pipe(Effect.orDie) + const workflowRef = + typeof evt.model.options.aisdk.request.workflowRef === "string" + ? evt.model.options.aisdk.request.workflowRef + : undefined + const workflowDefinition = + typeof evt.model.options.aisdk.request.workflowDefinition === "string" + ? evt.model.options.aisdk.request.workflowDefinition + : undefined + const language = evt.sdk.workflowChat( + gitlab.isWorkflowModel(evt.model.apiID) ? evt.model.apiID : "duo-workflow", + { + featureFlags, + workflowDefinition, + }, + ) + if (workflowRef) language.selectedModelRef = workflowRef + evt.language = language + return + } + evt.language = evt.sdk.agenticChat(evt.model.apiID, { + aiGatewayHeaders: evt.options.aiGatewayHeaders, + featureFlags, + }) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/google-vertex.ts b/packages/core/src/plugin/provider/google-vertex.ts new file mode 100644 index 000000000..f22f79f45 --- /dev/null +++ b/packages/core/src/plugin/provider/google-vertex.ts @@ -0,0 +1,124 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +function resolveProject(options: Record) { + // models.dev advertises GOOGLE_VERTEX_PROJECT for Vertex, while Google SDKs + // and ADC examples commonly use the broader Google Cloud project aliases. + return ( + options.project ?? + process.env.GOOGLE_VERTEX_PROJECT ?? + process.env.GOOGLE_CLOUD_PROJECT ?? + process.env.GCP_PROJECT ?? + process.env.GCLOUD_PROJECT + ) +} + +function resolveLocation(options: Record) { + return options.location ?? process.env.GOOGLE_VERTEX_LOCATION ?? process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "us-central1" +} + +function vertexEndpoint(location: string) { + return location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com` +} + +function replaceVertexVars(value: string, project: string | undefined, location: string) { + // Vertex OpenAI-compatible endpoints are stored as templates in the catalog; + // expand them after provider config/env project and location have been resolved. + return value + .replaceAll("${GOOGLE_VERTEX_PROJECT}", project ?? "${GOOGLE_VERTEX_PROJECT}") + .replaceAll("${GOOGLE_VERTEX_LOCATION}", location) + .replaceAll("${GOOGLE_VERTEX_ENDPOINT}", vertexEndpoint(location)) +} + +function authFetch(fetchWithRuntimeOptions?: unknown) { + // Native Vertex SDKs handle ADC internally. OpenAI-compatible Vertex endpoints + // do not, so inject a Google access token into their fetch path. + return async (input: Parameters[0], init?: RequestInit) => { + const { GoogleAuth } = await import("google-auth-library") + 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 typeof fetchWithRuntimeOptions === "function" + ? fetchWithRuntimeOptions(input, { ...init, headers }) + : fetch(input, { ...init, headers }) + } +} + +export const GoogleVertexPlugin = PluginV2.define({ + id: PluginV2.ID.make("google-vertex"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.googleVertex) return + const project = resolveProject(evt.provider.options.aisdk.provider) + const location = String(resolveLocation(evt.provider.options.aisdk.provider)) + if (project) evt.provider.options.aisdk.provider.project = project + evt.provider.options.aisdk.provider.location = location + if (evt.provider.endpoint.type === "aisdk" && evt.provider.endpoint.url) { + evt.provider.endpoint.url = replaceVertexVars(evt.provider.endpoint.url, project, location) + } + if (evt.provider.endpoint.type === "aisdk" && evt.provider.endpoint.package.includes("@ai-sdk/openai-compatible")) { + evt.provider.options.aisdk.provider.fetch = authFetch(evt.provider.options.aisdk.provider.fetch) + } + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.model.providerID === ProviderV2.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) { + evt.options.fetch = authFetch(evt.options.fetch) + return + } + if (evt.package !== "@ai-sdk/google-vertex") return + const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex")) + const project = resolveProject(evt.options) + const location = resolveLocation(evt.options) + const options = { ...evt.options } + delete options.fetch + evt.sdk = mod.createVertex({ + ...options, + project, + location, + }) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.googleVertex) return + evt.language = evt.sdk.languageModel(String(evt.model.apiID).trim()) + }), + } + }), +}) + +export const GoogleVertexAnthropicPlugin = PluginV2.define({ + id: PluginV2.ID.make("google-vertex-anthropic"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.make("google-vertex-anthropic")) return + const project = evt.provider.options.aisdk.provider.project ?? process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCP_PROJECT ?? process.env.GCLOUD_PROJECT + const location = evt.provider.options.aisdk.provider.location ?? process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "global" + if (project) evt.provider.options.aisdk.provider.project = project + evt.provider.options.aisdk.provider.location = location + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/google-vertex/anthropic") return + const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic")) + evt.sdk = mod.createVertexAnthropic({ + ...evt.options, + project: + typeof evt.options.project === "string" + ? evt.options.project + : (process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCP_PROJECT ?? process.env.GCLOUD_PROJECT), + location: + typeof evt.options.location === "string" + ? evt.options.location + : (process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "global"), + }) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return + evt.language = evt.sdk.languageModel(String(evt.model.apiID).trim()) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/google.ts b/packages/core/src/plugin/provider/google.ts new file mode 100644 index 000000000..47e29c6b5 --- /dev/null +++ b/packages/core/src/plugin/provider/google.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const GooglePlugin = PluginV2.define({ + id: PluginV2.ID.make("google"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/google") return + const mod = yield* Effect.promise(() => import("@ai-sdk/google")) + evt.sdk = mod.createGoogleGenerativeAI(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/groq.ts b/packages/core/src/plugin/provider/groq.ts new file mode 100644 index 000000000..f2052afd1 --- /dev/null +++ b/packages/core/src/plugin/provider/groq.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const GroqPlugin = PluginV2.define({ + id: PluginV2.ID.make("groq"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/groq") return + const mod = yield* Effect.promise(() => import("@ai-sdk/groq")) + evt.sdk = mod.createGroq(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/index.ts b/packages/core/src/plugin/provider/index.ts new file mode 100644 index 000000000..fd02d322a --- /dev/null +++ b/packages/core/src/plugin/provider/index.ts @@ -0,0 +1,67 @@ +import { AlibabaPlugin } from "./alibaba" +import { AmazonBedrockPlugin } from "./amazon-bedrock" +import { AnthropicPlugin } from "./anthropic" +import { AzureCognitiveServicesPlugin, AzurePlugin } from "./azure" +import { CerebrasPlugin } from "./cerebras" +import { CloudflareAIGatewayPlugin } from "./cloudflare-ai-gateway" +import { CloudflareWorkersAIPlugin } from "./cloudflare-workers-ai" +import { CoherePlugin } from "./cohere" +import { DeepInfraPlugin } from "./deepinfra" +import { DynamicProviderPlugin } from "./dynamic" +import { GatewayPlugin } from "./gateway" +import { GithubCopilotPlugin } from "./github-copilot" +import { GitLabPlugin } from "./gitlab" +import { GooglePlugin } from "./google" +import { GoogleVertexAnthropicPlugin, GoogleVertexPlugin } from "./google-vertex" +import { GroqPlugin } from "./groq" +import { KiloPlugin } from "./kilo" +import { LLMGatewayPlugin } from "./llmgateway" +import { MistralPlugin } from "./mistral" +import { NvidiaPlugin } from "./nvidia" +import { OpenAIPlugin } from "./openai" +import { OpenAICompatiblePlugin } from "./openai-compatible" +import { OpencodePlugin } from "./opencode" +import { OpenRouterPlugin } from "./openrouter" +import { PerplexityPlugin } from "./perplexity" +import { SapAICorePlugin } from "./sap-ai-core" +import { TogetherAIPlugin } from "./togetherai" +import { VercelPlugin } from "./vercel" +import { VenicePlugin } from "./venice" +import { XAIPlugin } from "./xai" +import { ZenmuxPlugin } from "./zenmux" + +export const ProviderPlugins = [ + AlibabaPlugin, + AmazonBedrockPlugin, + AnthropicPlugin, + AzureCognitiveServicesPlugin, + AzurePlugin, + CerebrasPlugin, + CloudflareAIGatewayPlugin, + CloudflareWorkersAIPlugin, + CoherePlugin, + DeepInfraPlugin, + GatewayPlugin, + GithubCopilotPlugin, + GitLabPlugin, + GooglePlugin, + GoogleVertexAnthropicPlugin, + GoogleVertexPlugin, + GroqPlugin, + KiloPlugin, + LLMGatewayPlugin, + MistralPlugin, + NvidiaPlugin, + OpencodePlugin, + OpenAICompatiblePlugin, + OpenAIPlugin, + OpenRouterPlugin, + PerplexityPlugin, + SapAICorePlugin, + TogetherAIPlugin, + VercelPlugin, + VenicePlugin, + XAIPlugin, + ZenmuxPlugin, + DynamicProviderPlugin, +] diff --git a/packages/core/src/plugin/provider/kilo.ts b/packages/core/src/plugin/provider/kilo.ts new file mode 100644 index 000000000..47b8ec99c --- /dev/null +++ b/packages/core/src/plugin/provider/kilo.ts @@ -0,0 +1,16 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const KiloPlugin = PluginV2.define({ + id: PluginV2.ID.make("kilo"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.make("kilo")) return + evt.provider.options.headers["HTTP-Referer"] = "https://opencode.ai/" + evt.provider.options.headers["X-Title"] = "opencode" + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/llmgateway.ts b/packages/core/src/plugin/provider/llmgateway.ts new file mode 100644 index 000000000..da1ab282b --- /dev/null +++ b/packages/core/src/plugin/provider/llmgateway.ts @@ -0,0 +1,18 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const LLMGatewayPlugin = PluginV2.define({ + id: PluginV2.ID.make("llmgateway"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.make("llmgateway")) return + if (evt.provider.enabled === false) return + evt.provider.options.headers["HTTP-Referer"] = "https://opencode.ai/" + evt.provider.options.headers["X-Title"] = "opencode" + evt.provider.options.headers["X-Source"] = "opencode" + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/mistral.ts b/packages/core/src/plugin/provider/mistral.ts new file mode 100644 index 000000000..e7f0decb7 --- /dev/null +++ b/packages/core/src/plugin/provider/mistral.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const MistralPlugin = PluginV2.define({ + id: PluginV2.ID.make("mistral"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/mistral") return + const mod = yield* Effect.promise(() => import("@ai-sdk/mistral")) + evt.sdk = mod.createMistral(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/nvidia.ts b/packages/core/src/plugin/provider/nvidia.ts new file mode 100644 index 000000000..b227e5cef --- /dev/null +++ b/packages/core/src/plugin/provider/nvidia.ts @@ -0,0 +1,16 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const NvidiaPlugin = PluginV2.define({ + id: PluginV2.ID.make("nvidia"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.make("nvidia")) return + evt.provider.options.headers["HTTP-Referer"] = "https://opencode.ai/" + evt.provider.options.headers["X-Title"] = "opencode" + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/openai-compatible.ts b/packages/core/src/plugin/provider/openai-compatible.ts new file mode 100644 index 000000000..76c337370 --- /dev/null +++ b/packages/core/src/plugin/provider/openai-compatible.ts @@ -0,0 +1,17 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const OpenAICompatiblePlugin = PluginV2.define({ + id: PluginV2.ID.make("openai-compatible"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.sdk) return + if (!evt.package.includes("@ai-sdk/openai-compatible")) return + if (evt.options.includeUsage !== false) evt.options.includeUsage = true + const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible")) + evt.sdk = mod.createOpenAICompatible(evt.options as any) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts new file mode 100644 index 000000000..a81455f19 --- /dev/null +++ b/packages/core/src/plugin/provider/openai.ts @@ -0,0 +1,27 @@ +import { Effect } from "effect" +import { ModelV2 } from "../../model" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const OpenAIPlugin = PluginV2.define({ + id: PluginV2.ID.make("openai"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/openai") return + const mod = yield* Effect.promise(() => import("@ai-sdk/openai")) + evt.sdk = mod.createOpenAI(evt.options) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.openai) return + evt.language = evt.sdk.responses(evt.model.apiID) + }), + "model.update": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.openai) return + // OpenAIPlugin sends OpenAI models through Responses; this alias is a + // chat-completions-only model, so remove it only from OpenAI's catalog. + if (evt.model.id === ModelV2.ID.make("gpt-5-chat-latest")) evt.cancel = true + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts new file mode 100644 index 000000000..44c904aec --- /dev/null +++ b/packages/core/src/plugin/provider/opencode.ts @@ -0,0 +1,27 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const OpencodePlugin = PluginV2.define({ + id: PluginV2.ID.make("opencode"), + effect: Effect.gen(function* () { + let hasKey = false + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.opencode) return + hasKey = Boolean( + process.env.OPENCODE_API_KEY || + evt.provider.env.some((item) => process.env[item]) || + evt.provider.options.aisdk.provider.apiKey || + (evt.provider.enabled && evt.provider.enabled.via === "auth"), + ) + if (!hasKey) evt.provider.options.aisdk.provider.apiKey = "public" + }), + "model.update": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.opencode) return + if (hasKey) return + if (evt.model.cost.some((item) => item.input > 0)) evt.cancel = true + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/openrouter.ts b/packages/core/src/plugin/provider/openrouter.ts new file mode 100644 index 000000000..976eea8c0 --- /dev/null +++ b/packages/core/src/plugin/provider/openrouter.ts @@ -0,0 +1,29 @@ +import { Effect } from "effect" +import { ModelV2 } from "../../model" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const OpenRouterPlugin = PluginV2.define({ + id: PluginV2.ID.make("openrouter"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.openrouter) return + evt.provider.options.headers["HTTP-Referer"] = "https://opencode.ai/" + evt.provider.options.headers["X-Title"] = "opencode" + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@openrouter/ai-sdk-provider") return + const mod = yield* Effect.promise(() => import("@openrouter/ai-sdk-provider")) + evt.sdk = mod.createOpenRouter(evt.options) + }), + "model.update": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.openrouter) return + // These are OpenRouter-specific OpenAI chat aliases that do not work on + // the generic path. Keep custom providers with matching IDs untouched. + if (evt.model.id === ModelV2.ID.make("gpt-5-chat-latest")) evt.cancel = true + if (evt.model.id === ModelV2.ID.make("openai/gpt-5-chat")) evt.cancel = true + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/perplexity.ts b/packages/core/src/plugin/provider/perplexity.ts new file mode 100644 index 000000000..2415ab7c1 --- /dev/null +++ b/packages/core/src/plugin/provider/perplexity.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const PerplexityPlugin = PluginV2.define({ + id: PluginV2.ID.make("perplexity"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/perplexity") return + const mod = yield* Effect.promise(() => import("@ai-sdk/perplexity")) + evt.sdk = mod.createPerplexity(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/sap-ai-core.ts b/packages/core/src/plugin/provider/sap-ai-core.ts new file mode 100644 index 000000000..619f01eb3 --- /dev/null +++ b/packages/core/src/plugin/provider/sap-ai-core.ts @@ -0,0 +1,40 @@ +import { Npm } from "../../npm" +import { Effect, Option } from "effect" +import { pathToFileURL } from "url" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const SapAICorePlugin = PluginV2.define({ + id: PluginV2.ID.make("sap-ai-core"), + effect: Effect.gen(function* () { + const npm = yield* Npm.Service + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return + const serviceKey = + process.env.AICORE_SERVICE_KEY ?? + (typeof evt.options.serviceKey === "string" ? evt.options.serviceKey : undefined) + if (serviceKey && !process.env.AICORE_SERVICE_KEY) process.env.AICORE_SERVICE_KEY = serviceKey + + const installedPath = evt.package.startsWith("file://") + ? evt.package + : Option.getOrUndefined((yield* npm.add(evt.package).pipe(Effect.orDie)).entrypoint) + if (!installedPath) throw new Error(`Package ${evt.package} has no import entrypoint`) + + const mod = yield* Effect.promise(async () => { + return (await import( + installedPath.startsWith("file://") ? installedPath : pathToFileURL(installedPath).href + )) as Record any> + }).pipe(Effect.orDie) + const match = Object.keys(mod).find((name) => name.startsWith("create")) + if (!match) throw new Error(`Package ${evt.package} has no provider factory export`) + + evt.sdk = mod[match](serviceKey ? { deploymentId: process.env.AICORE_DEPLOYMENT_ID, resourceGroup: process.env.AICORE_RESOURCE_GROUP } : {}) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return + evt.language = evt.sdk(evt.model.apiID) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/togetherai.ts b/packages/core/src/plugin/provider/togetherai.ts new file mode 100644 index 000000000..b1870f266 --- /dev/null +++ b/packages/core/src/plugin/provider/togetherai.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const TogetherAIPlugin = PluginV2.define({ + id: PluginV2.ID.make("togetherai"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/togetherai") return + const mod = yield* Effect.promise(() => import("@ai-sdk/togetherai")) + evt.sdk = mod.createTogetherAI(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/venice.ts b/packages/core/src/plugin/provider/venice.ts new file mode 100644 index 000000000..8a3b95024 --- /dev/null +++ b/packages/core/src/plugin/provider/venice.ts @@ -0,0 +1,15 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" + +export const VenicePlugin = PluginV2.define({ + id: PluginV2.ID.make("venice"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "venice-ai-sdk-provider") return + const mod = yield* Effect.promise(() => import("venice-ai-sdk-provider")) + evt.sdk = mod.createVenice(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/vercel.ts b/packages/core/src/plugin/provider/vercel.ts new file mode 100644 index 000000000..2108542b1 --- /dev/null +++ b/packages/core/src/plugin/provider/vercel.ts @@ -0,0 +1,21 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const VercelPlugin = PluginV2.define({ + id: PluginV2.ID.make("vercel"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.make("vercel")) return + evt.provider.options.headers["http-referer"] = "https://opencode.ai/" + evt.provider.options.headers["x-title"] = "opencode" + }), + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/vercel") return + const mod = yield* Effect.promise(() => import("@ai-sdk/vercel")) + evt.sdk = mod.createVercel(evt.options) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/xai.ts b/packages/core/src/plugin/provider/xai.ts new file mode 100644 index 000000000..b54aa7374 --- /dev/null +++ b/packages/core/src/plugin/provider/xai.ts @@ -0,0 +1,20 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const XAIPlugin = PluginV2.define({ + id: PluginV2.ID.make("xai"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (evt.package !== "@ai-sdk/xai") return + const mod = yield* Effect.promise(() => import("@ai-sdk/xai")) + evt.sdk = mod.createXai(evt.options) + }), + "aisdk.language": Effect.fn(function* (evt) { + if (evt.model.providerID !== ProviderV2.ID.make("xai")) return + evt.language = evt.sdk.responses(evt.model.apiID) + }), + } + }), +}) diff --git a/packages/core/src/plugin/provider/zenmux.ts b/packages/core/src/plugin/provider/zenmux.ts new file mode 100644 index 000000000..6bdd42601 --- /dev/null +++ b/packages/core/src/plugin/provider/zenmux.ts @@ -0,0 +1,16 @@ +import { Effect } from "effect" +import { PluginV2 } from "../../plugin" +import { ProviderV2 } from "../../provider" + +export const ZenmuxPlugin = PluginV2.define({ + id: PluginV2.ID.make("zenmux"), + effect: Effect.gen(function* () { + return { + "provider.update": Effect.fn(function* (evt) { + if (evt.provider.id !== ProviderV2.ID.make("zenmux")) return + evt.provider.options.headers["HTTP-Referer"] ??= "https://opencode.ai/" + evt.provider.options.headers["X-Title"] ??= "opencode" + }), + } + }), +}) diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts new file mode 100644 index 000000000..7c1c96665 --- /dev/null +++ b/packages/core/src/provider.ts @@ -0,0 +1,120 @@ +export * as ProviderV2 from "./provider" + +import { withStatics } from "./schema" +import { Schema } from "effect" + +export const ID = Schema.String.pipe( + Schema.brand("ProviderV2.ID"), + withStatics((schema) => ({ + // Well-known providers + opencode: schema.make("opencode"), + anthropic: schema.make("anthropic"), + openai: schema.make("openai"), + google: schema.make("google"), + googleVertex: schema.make("google-vertex"), + githubCopilot: schema.make("github-copilot"), + amazonBedrock: schema.make("amazon-bedrock"), + azure: schema.make("azure"), + openrouter: schema.make("openrouter"), + mistral: schema.make("mistral"), + gitlab: schema.make("gitlab"), + })), +) +export type ID = typeof ID.Type + +const OpenAIResponses = Schema.Struct({ + type: Schema.Literal("openai/responses"), + url: Schema.String, + websocket: Schema.optional(Schema.Boolean), +}) + +const OpenAICompletions = Schema.Struct({ + type: Schema.Literal("openai/completions"), + url: Schema.String, + reasoning: Schema.Union([ + Schema.Struct({ + type: Schema.Literal("reasoning_content"), + }), + Schema.Struct({ + type: Schema.Literal("reasoning_details"), + }), + ]).pipe(Schema.optional), +}) +export type OpenAICompletions = typeof OpenAICompletions.Type + +const AISDK = Schema.Struct({ + type: Schema.Literal("aisdk"), + package: Schema.String, + url: Schema.String.pipe(Schema.optional), +}) + +const AnthropicMessages = Schema.Struct({ + type: Schema.Literal("anthropic/messages"), + url: Schema.String, +}) + +const UnknownEndpoint = Schema.Struct({ + type: Schema.Literal("unknown"), +}) + +export const Endpoint = Schema.Union([ + UnknownEndpoint, + OpenAIResponses, + OpenAICompletions, + AnthropicMessages, + AISDK, +]).pipe(Schema.toTaggedUnion("type")) +export type Endpoint = typeof Endpoint.Type + +export const Options = Schema.Struct({ + headers: Schema.Record(Schema.String, Schema.String), + body: Schema.Record(Schema.String, Schema.Any), + aisdk: Schema.Struct({ + provider: Schema.Record(Schema.String, Schema.Any), + request: Schema.Record(Schema.String, Schema.Any), + }), +}) +export type Options = typeof Options.Type + +export class Info extends Schema.Class("ProviderV2.Info")({ + id: ID, + name: Schema.String, + enabled: Schema.Union([ + Schema.Literal(false), + Schema.Struct({ + via: Schema.Literal("env"), + name: Schema.String, + }), + Schema.Struct({ + via: Schema.Literal("auth"), + service: Schema.String, + }), + Schema.Struct({ + via: Schema.Literal("custom"), + data: Schema.Record(Schema.String, Schema.Any), + }), + ]), + env: Schema.String.pipe(Schema.Array), + endpoint: Endpoint, + options: Options, +}) { + static empty(providerID: ID) { + return new Info({ + id: providerID, + name: providerID, + enabled: false, + env: [], + endpoint: { + type: "unknown", + }, + options: { + headers: {}, + body: {}, + aisdk: { + provider: {}, + request: {}, + }, + }, + }) + } +} diff --git a/packages/opencode/src/v2/session-prompt.ts b/packages/core/src/session-prompt.ts similarity index 100% rename from packages/opencode/src/v2/session-prompt.ts rename to packages/core/src/session-prompt.ts diff --git a/packages/opencode/src/v2/tool-output.ts b/packages/core/src/tool-output.ts similarity index 100% rename from packages/opencode/src/v2/tool-output.ts rename to packages/core/src/tool-output.ts diff --git a/packages/opencode/src/v2/schema.ts b/packages/core/src/v2-schema.ts similarity index 88% rename from packages/opencode/src/v2/schema.ts rename to packages/core/src/v2-schema.ts index 44587b838..a34b0b151 100644 --- a/packages/opencode/src/v2/schema.ts +++ b/packages/core/src/v2-schema.ts @@ -7,4 +7,4 @@ export const DateTimeUtcFromMillis = Schema.Finite.pipe( }), ) -export * as V2Schema from "./schema" +export * as V2Schema from "./v2-schema" diff --git a/packages/opencode/test/provider/copilot/convert-to-copilot-messages.test.ts b/packages/core/test/github-copilot/convert-to-copilot-messages.test.ts similarity index 99% rename from packages/opencode/test/provider/copilot/convert-to-copilot-messages.test.ts rename to packages/core/test/github-copilot/convert-to-copilot-messages.test.ts index 6f874db6d..65f4b6a53 100644 --- a/packages/opencode/test/provider/copilot/convert-to-copilot-messages.test.ts +++ b/packages/core/test/github-copilot/convert-to-copilot-messages.test.ts @@ -1,4 +1,4 @@ -import { convertToOpenAICompatibleChatMessages as convertToCopilotMessages } from "@/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages" +import { convertToOpenAICompatibleChatMessages as convertToCopilotMessages } from "@opencode-ai/core/github-copilot/chat/convert-to-openai-compatible-chat-messages" import { describe, test, expect } from "bun:test" describe("system messages", () => { diff --git a/packages/opencode/test/provider/copilot/copilot-chat-model.test.ts b/packages/core/test/github-copilot/copilot-chat-model.test.ts similarity index 99% rename from packages/opencode/test/provider/copilot/copilot-chat-model.test.ts rename to packages/core/test/github-copilot/copilot-chat-model.test.ts index 389a72bb3..bc1e2ecd9 100644 --- a/packages/opencode/test/provider/copilot/copilot-chat-model.test.ts +++ b/packages/core/test/github-copilot/copilot-chat-model.test.ts @@ -1,4 +1,4 @@ -import { OpenAICompatibleChatLanguageModel } from "@/provider/sdk/copilot/chat/openai-compatible-chat-language-model" +import { OpenAICompatibleChatLanguageModel } from "@opencode-ai/core/github-copilot/chat/openai-compatible-chat-language-model" import { describe, test, expect, mock } from "bun:test" import type { LanguageModelV3Prompt } from "@ai-sdk/provider" diff --git a/packages/core/test/plugin/provider-github-copilot.test.ts b/packages/core/test/plugin/provider-github-copilot.test.ts new file mode 100644 index 000000000..c825f7b8e --- /dev/null +++ b/packages/core/test/plugin/provider-github-copilot.test.ts @@ -0,0 +1,188 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { GithubCopilotPlugin } from "@opencode-ai/core/plugin/provider/github-copilot" +import { fakeSelectorSdk, it, model } from "../v2/plugin/provider-helper" + +describe("GithubCopilotPlugin", () => { + it.effect("creates the bundled Copilot SDK for the GitHub Copilot package", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GithubCopilotPlugin) + const ignored = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("github-copilot", "gpt-5"), + package: "@ai-sdk/openai-compatible", + options: { name: "github-copilot" }, + }, + {}, + ) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("github-copilot", "gpt-5"), + package: "@ai-sdk/github-copilot", + options: { name: "github-copilot" }, + }, + {}, + ) + expect(ignored.sdk).toBeUndefined() + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("selects languageModel when responses and chat are absent", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(GithubCopilotPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("github-copilot", "claude-sonnet-4"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: {}, + }, + {}, + ) + expect(calls).toEqual(["languageModel:claude-sonnet-4"]) + }), + ) + + it.effect("selects languageModel with the API model ID when responses and chat are absent", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(GithubCopilotPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("github-copilot", "alias", { apiID: ModelV2.ID.make("claude-sonnet-4") }), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: {}, + }, + {}, + ) + expect(calls).toEqual(["languageModel:claude-sonnet-4"]) + }), + ) + + it.effect("uses responses for gpt-5 models except gpt-5-mini", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(GithubCopilotPlugin) + yield* plugin.trigger( + "aisdk.language", + { model: model("github-copilot", "gpt-5"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { model: model("github-copilot", "gpt-5.1-codex"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { model: model("github-copilot", "gpt-4o"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { model: model("github-copilot", "gpt-5-mini"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { model: model("github-copilot", "gpt-5-mini-2025-08-07"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + expect(calls).toEqual([ + "responses:gpt-5", + "responses:gpt-5.1-codex", + "chat:gpt-4o", + "chat:gpt-5-mini", + "chat:gpt-5-mini-2025-08-07", + ]) + }), + ) + + it.effect("uses the API model ID when selecting responses or chat", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(GithubCopilotPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("github-copilot", "default", { apiID: ModelV2.ID.make("gpt-5") }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { + model: model("github-copilot", "small", { apiID: ModelV2.ID.make("gpt-5-mini") }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { + model: model("github-copilot", "sonnet", { apiID: ModelV2.ID.make("claude-sonnet-4") }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + expect(calls).toEqual(["responses:gpt-5", "chat:gpt-5-mini", "chat:claude-sonnet-4"]) + }), + ) + + it.effect("filters gpt-5-chat-latest before Copilot language selection", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GithubCopilotPlugin) + const result = yield* plugin.trigger( + "model.update", + {}, + { model: model("github-copilot", "gpt-5-chat-latest"), cancel: false }, + ) + expect(result.cancel).toBe(true) + }), + ) + + it.effect("does not filter gpt-5-chat-latest for non-Copilot providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GithubCopilotPlugin) + const result = yield* plugin.trigger( + "model.update", + {}, + { model: model("custom-copilot", "gpt-5-chat-latest"), cancel: false }, + ) + expect(result.cancel).toBe(false) + }), + ) + + it.effect("ignores non-Copilot providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(GithubCopilotPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { model: model("openai", "gpt-5"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + expect(calls).toEqual([]) + expect(result.language).toBeUndefined() + }), + ) +}) diff --git a/packages/core/test/v2/catalog.test.ts b/packages/core/test/v2/catalog.test.ts new file mode 100644 index 000000000..cba3405bc --- /dev/null +++ b/packages/core/test/v2/catalog.test.ts @@ -0,0 +1,199 @@ +import { describe, expect } from "bun:test" +import { DateTime, Effect, Layer, Option } from "effect" +import { Catalog } from "@opencode-ai/core/catalog" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { testEffect } from "../lib/effect" + +const it = testEffect(Catalog.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer))) + +describe("CatalogV2", () => { + it.effect("normalizes provider baseURL into endpoint url", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.make("test") + + yield* catalog.provider.update(providerID, (provider) => { + provider.endpoint = { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://default.example.com", + } + provider.options.aisdk.provider.baseURL = "https://override.example.com" + }) + + const provider = yield* catalog.provider.get(providerID) + + expect(provider.endpoint).toEqual({ + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://override.example.com", + }) + expect(provider.options.aisdk.provider.baseURL).toBeUndefined() + }), + ) + + it.effect("normalizes model baseURL into endpoint url", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.make("test") + const modelID = ModelV2.ID.make("model") + + yield* catalog.provider.update(providerID, (provider) => { + provider.endpoint = { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://provider.example.com", + } + }) + yield* catalog.model.update(providerID, modelID, (model) => { + model.endpoint = { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://model.example.com", + } + model.options.aisdk.provider.baseURL = "https://override.example.com" + }) + + const model = yield* catalog.model.get(providerID, modelID) + + expect(model.endpoint).toEqual({ + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://override.example.com", + }) + expect(model.options.aisdk.provider.baseURL).toBeUndefined() + }), + ) + + it.effect("resolves unknown model endpoint from provider endpoint", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.make("test") + const modelID = ModelV2.ID.make("model") + + yield* catalog.provider.update(providerID, (provider) => { + provider.endpoint = { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://provider.example.com", + } + }) + yield* catalog.model.update(providerID, modelID, () => {}) + + const model = yield* catalog.model.get(providerID, modelID) + + expect(model.endpoint).toEqual({ + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://provider.example.com", + }) + }), + ) + + it.effect("runs provider hooks after baseURL is normalized", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const plugin = yield* PluginV2.Service + const providerID = ProviderV2.ID.make("test") + const seen: unknown[] = [] + + yield* plugin.add({ + id: PluginV2.ID.make("test"), + effect: Effect.succeed({ + "provider.update": (evt) => + Effect.sync(() => { + seen.push(evt.provider.endpoint.type) + if (evt.provider.endpoint.type === "aisdk") seen.push(evt.provider.endpoint.url) + seen.push(evt.provider.options.aisdk.provider.baseURL) + }), + }), + }) + yield* catalog.provider.update(providerID, (provider) => { + provider.endpoint = { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + } + provider.options.aisdk.provider.baseURL = "https://provider.example.com" + }) + + expect(seen).toEqual(["aisdk", "https://provider.example.com", undefined]) + }), + ) + + it.effect("resolves provider and model option merges", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.make("test") + const modelID = ModelV2.ID.make("model") + + yield* catalog.provider.update(providerID, (provider) => { + provider.options.headers.provider = "provider" + provider.options.headers.shared = "provider" + provider.options.body.provider = true + provider.options.aisdk.provider.provider = true + }) + yield* catalog.model.update(providerID, modelID, (model) => { + model.options.headers.model = "model" + model.options.headers.shared = "model" + model.options.body.model = true + model.options.aisdk.provider.model = true + model.options.aisdk.request.request = true + }) + + const model = yield* catalog.model.get(providerID, modelID) + + expect(model.options.headers).toEqual({ provider: "provider", shared: "model", model: "model" }) + expect(model.options.body).toEqual({ provider: true, model: true }) + expect(model.options.aisdk.provider).toEqual({ provider: true, model: true }) + expect(model.options.aisdk.request).toEqual({ request: true }) + }), + ) + + it.effect("falls back to newest available model when no default is configured", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.make("test") + + yield* catalog.provider.update(providerID, (provider) => { + provider.enabled = { via: "custom", data: {} } + }) + yield* catalog.model.update(providerID, ModelV2.ID.make("old"), (model) => { + model.time.released = DateTime.makeUnsafe(1000) + }) + yield* catalog.model.update(providerID, ModelV2.ID.make("new"), (model) => { + model.time.released = DateTime.makeUnsafe(2000) + }) + + const model = yield* catalog.model.default() + + expect(Option.getOrUndefined(model)?.id).toMatch("new") + }), + ) + + it.effect("small model prefers small keyword candidates before cost scoring", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.make("test") + + yield* catalog.provider.update(providerID, () => {}) + yield* catalog.model.update(providerID, ModelV2.ID.make("cheap-large"), (model) => { + model.capabilities.input = ["text"] + model.capabilities.output = ["text"] + model.cost = [{ input: 1, output: 1, cache: { read: 0, write: 0 } }] + model.time.released = DateTime.makeUnsafe(Date.now()) + }) + yield* catalog.model.update(providerID, ModelV2.ID.make("expensive-mini"), (model) => { + model.capabilities.input = ["text"] + model.capabilities.output = ["text"] + model.cost = [{ input: 10, output: 10, cache: { read: 0, write: 0 } }] + model.time.released = DateTime.makeUnsafe(Date.now()) + }) + + const model = yield* catalog.model.small(providerID) + + expect(Option.getOrUndefined(model)?.id).toMatch("expensive-mini") + }), + ) +}) diff --git a/packages/core/test/v2/plugin/fixtures/provider-factory.ts b/packages/core/test/v2/plugin/fixtures/provider-factory.ts new file mode 100644 index 000000000..7278c231d --- /dev/null +++ b/packages/core/test/v2/plugin/fixtures/provider-factory.ts @@ -0,0 +1,9 @@ +export function createFixtureProvider(options: Record) { + const captured = Object.fromEntries(Object.entries(options)) + return Object.assign((modelID: string) => ({ modelID, options: captured }), { + options: captured, + languageModel(modelID: string) { + return { modelID, options: captured } + }, + }) +} diff --git a/packages/core/test/v2/plugin/provider-alibaba.test.ts b/packages/core/test/v2/plugin/provider-alibaba.test.ts new file mode 100644 index 000000000..06e6f969f --- /dev/null +++ b/packages/core/test/v2/plugin/provider-alibaba.test.ts @@ -0,0 +1,67 @@ +import { describe, expect } from "bun:test" +import { createAlibaba } from "@ai-sdk/alibaba" +import { Effect } from "effect" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { AlibabaPlugin } from "@opencode-ai/core/plugin/provider/alibaba" +import { it, model } from "./provider-helper" + +describe("AlibabaPlugin", () => { + it.effect("creates an Alibaba SDK for @ai-sdk/alibaba", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AlibabaPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("alibaba", "qwen"), package: "@ai-sdk/alibaba", options: { name: "alibaba" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("ignores non-Alibaba SDK packages", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AlibabaPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("alibaba", "qwen"), package: "@ai-sdk/openai-compatible", options: { name: "alibaba" } }, + {}, + ) + expect(result.sdk).toBeUndefined() + }), + ) + + it.effect("matches the old bundled Alibaba SDK provider naming", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AlibabaPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-alibaba", "qwen"), + package: "@ai-sdk/alibaba", + options: { name: "custom-alibaba", apiKey: "test" }, + }, + {}, + ) + const expected = createAlibaba({ apiKey: "test", ...{ name: "custom-alibaba" } }).languageModel("qwen") + const actual = result.sdk?.languageModel("qwen") + expect(actual?.provider).toBe(expected.provider) + expect(actual?.modelId).toBe(expected.modelId) + }), + ) + + it.effect("uses the old default languageModel(apiID) behavior", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AlibabaPlugin) + const item = model("alibaba", "alias", { apiID: ModelV2.ID.make("qwen-plus") }) + const result = yield* plugin.trigger("aisdk.sdk", { model: item, package: "@ai-sdk/alibaba", options: {} }, {}) + const language = result.sdk?.languageModel(item.apiID) + expect(language?.modelId).toBe("qwen-plus") + expect(language?.provider).toBe("alibaba.chat") + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts b/packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts new file mode 100644 index 000000000..e7e53cb8d --- /dev/null +++ b/packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts @@ -0,0 +1,464 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { AmazonBedrockPlugin } from "@opencode-ai/core/plugin/provider/amazon-bedrock" +import { fakeSelectorSdk, it, model, provider, withEnv } from "./provider-helper" + +function bedrockBaseURL(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") { + const language = (sdk as { languageModel: (id: string) => unknown }).languageModel(modelID) + return (language as { config: { baseUrl: () => string } }).config.baseUrl() +} + +function bedrockFetch(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") { + const language = (sdk as { languageModel: (id: string) => unknown }).languageModel(modelID) + return (language as { config: { fetch: (input: Parameters[0], init?: RequestInit) => Promise } }).config + .fetch +} + +describe("AmazonBedrockPlugin", () => { + it.effect("moves endpoint option to endpoint URL", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("amazon-bedrock", { + options: { + headers: {}, + body: {}, + aisdk: { provider: { endpoint: "https://bedrock.example" }, request: {} }, + }, + }), + cancel: false, + }, + ) + expect(result.provider.endpoint).toEqual({ + type: "aisdk", + package: "test-provider", + url: "https://bedrock.example", + }) + expect(result.provider.options.aisdk.provider.endpoint).toBeUndefined() + }), + ) + + it.effect("prefers endpoint over baseURL for SDK base URL", () => + withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { + name: "amazon-bedrock", + bearerToken: "token", + baseURL: "https://base.example", + endpoint: "https://endpoint.example", + region: "us-east-1", + }, + }, + {}, + ) + expect(bedrockBaseURL(result.sdk)).toBe("https://endpoint.example") + }), + ), + ) + + it.effect("uses baseURL as SDK base URL", () => + withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { + name: "amazon-bedrock", + bearerToken: "token", + baseURL: "https://base.example", + region: "us-east-1", + }, + }, + {}, + ) + expect(bedrockBaseURL(result.sdk)).toBe("https://base.example") + }), + ), + ) + + it.effect("creates SDK without explicit credential env so the default AWS chain can resolve credentials", () => + withEnv( + { + AWS_ACCESS_KEY_ID: undefined, + AWS_BEARER_TOKEN_BEDROCK: undefined, + AWS_CONTAINER_CREDENTIALS_FULL_URI: undefined, + AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: undefined, + AWS_PROFILE: undefined, + AWS_REGION: undefined, + AWS_WEB_IDENTITY_TOKEN_FILE: undefined, + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { name: "amazon-bedrock" }, + }, + {}, + ) + expect(result.sdk).toBeDefined() + expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.us-east-1.amazonaws.com") + }), + ), + ) + + it.effect("uses config region over AWS_REGION for SDK base URL", () => + withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: "us-east-1" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { name: "amazon-bedrock", region: "eu-west-1" }, + }, + {}, + ) + expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.eu-west-1.amazonaws.com") + }), + ), + ) + + it.effect("uses AWS_REGION for SDK base URL when config region is absent", () => + withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: "eu-west-1" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { name: "amazon-bedrock" }, + }, + {}, + ) + expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.eu-west-1.amazonaws.com") + }), + ), + ) + + it.effect("defaults SDK region to us-east-1", () => + withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { name: "amazon-bedrock" }, + }, + {}, + ) + expect(bedrockBaseURL(result.sdk)).toBe("https://bedrock-runtime.us-east-1.amazonaws.com") + }), + ), + ) + + it.effect("loads bearer token option into env and uses bearer auth", () => + withEnv({ AWS_ACCESS_KEY_ID: undefined, AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const headers: Array = [] + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { + name: "amazon-bedrock", + bearerToken: "option-token", + fetch: async (_input: Parameters[0], init?: RequestInit) => { + headers.push(new Headers(init?.headers).get("Authorization")) + return new Response("{}") + }, + }, + }, + {}, + ) + yield* Effect.promise(() => bedrockFetch(result.sdk)("https://bedrock.example", { method: "POST" })) + expect(process.env.AWS_BEARER_TOKEN_BEDROCK).toBe("option-token") + expect(headers).toEqual(["Bearer option-token"]) + }), + ), + ) + + it.effect("prefers bearer token env over bearer token option", () => + withEnv({ AWS_BEARER_TOKEN_BEDROCK: "env-token" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const headers: Array = [] + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { + name: "amazon-bedrock", + bearerToken: "option-token", + fetch: async (_input: Parameters[0], init?: RequestInit) => { + headers.push(new Headers(init?.headers).get("Authorization")) + return new Response("{}") + }, + }, + }, + {}, + ) + yield* Effect.promise(() => bedrockFetch(result.sdk)("https://bedrock.example", { method: "POST" })) + expect(process.env.AWS_BEARER_TOKEN_BEDROCK).toBe("env-token") + expect(headers).toEqual(["Bearer env-token"]) + }), + ), + ) + + it.effect("uses SigV4 credential env when bearer token is absent", () => + withEnv( + { + AWS_ACCESS_KEY_ID: "test-access-key", + AWS_BEARER_TOKEN_BEDROCK: undefined, + AWS_REGION: "us-east-1", + AWS_SECRET_ACCESS_KEY: "test-secret-key", + AWS_SESSION_TOKEN: "test-session-token", + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const headers: Array = [] + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + package: "@ai-sdk/amazon-bedrock", + options: { + name: "amazon-bedrock", + fetch: async (_input: Parameters[0], init?: RequestInit) => { + headers.push(new Headers(init?.headers).get("Authorization")) + return new Response("{}") + }, + }, + }, + {}, + ) + yield* Effect.promise(() => + bedrockFetch(result.sdk)("https://bedrock-runtime.us-east-1.amazonaws.com/model/test/invoke", { + body: "{}", + method: "POST", + }), + ) + expect(headers[0]?.startsWith("AWS4-HMAC-SHA256 ")).toBe(true) + }), + ), + ) + + it.effect("applies legacy cross-region inference prefixes", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AmazonBedrockPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: {}, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: { region: "eu-west-1" }, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { + model: model("amazon-bedrock", "global.anthropic.claude-sonnet-4-5"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: { region: "eu-west-1" }, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: { region: "ap-northeast-1" }, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: { region: "ap-southeast-2" }, + }, + {}, + ) + expect(calls).toEqual([ + "languageModel:us.anthropic.claude-sonnet-4-5", + "languageModel:eu.anthropic.claude-sonnet-4-5", + "languageModel:global.anthropic.claude-sonnet-4-5", + "languageModel:jp.anthropic.claude-sonnet-4-5", + "languageModel:au.anthropic.claude-sonnet-4-5", + ]) + }), + ) + + it.effect("uses AWS_REGION for language prefixes when region option is absent", () => + withEnv({ AWS_REGION: "eu-west-1" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AmazonBedrockPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("amazon-bedrock", "anthropic.claude-sonnet-4-5"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: {}, + }, + {}, + ) + expect(calls).toEqual(["languageModel:eu.anthropic.claude-sonnet-4-5"]) + }), + ), + ) + + it.effect("applies the full legacy cross-region prefix matrix", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + const cases = [ + { region: "us-east-1", modelID: "amazon.nova-micro-v1:0", expected: "us.amazon.nova-micro-v1:0" }, + { region: "us-east-1", modelID: "amazon.nova-lite-v1:0", expected: "us.amazon.nova-lite-v1:0" }, + { region: "us-east-1", modelID: "amazon.nova-pro-v1:0", expected: "us.amazon.nova-pro-v1:0" }, + { region: "us-east-1", modelID: "amazon.nova-premier-v1:0", expected: "us.amazon.nova-premier-v1:0" }, + { region: "us-east-1", modelID: "amazon.nova-2-lite-v1:0", expected: "us.amazon.nova-2-lite-v1:0" }, + { region: "us-east-1", modelID: "anthropic.claude-sonnet-4-5", expected: "us.anthropic.claude-sonnet-4-5" }, + { region: "us-east-1", modelID: "deepseek.r1-v1:0", expected: "us.deepseek.r1-v1:0" }, + { region: "us-gov-west-1", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" }, + { region: "us-east-1", modelID: "cohere.command-r-plus-v1:0", expected: "cohere.command-r-plus-v1:0" }, + { region: "eu-west-1", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" }, + { region: "eu-west-2", modelID: "amazon.nova-lite-v1:0", expected: "eu.amazon.nova-lite-v1:0" }, + { region: "eu-west-3", modelID: "amazon.nova-micro-v1:0", expected: "eu.amazon.nova-micro-v1:0" }, + { + region: "eu-north-1", + modelID: "meta.llama3-70b-instruct-v1:0", + expected: "eu.meta.llama3-70b-instruct-v1:0", + }, + { region: "eu-central-1", modelID: "mistral.pixtral-large-v1:0", expected: "eu.mistral.pixtral-large-v1:0" }, + { region: "eu-south-1", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" }, + { region: "eu-south-2", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" }, + { region: "eu-central-2", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" }, + { region: "eu-west-1", modelID: "cohere.command-r-plus-v1:0", expected: "cohere.command-r-plus-v1:0" }, + { + region: "ap-southeast-2", + modelID: "anthropic.claude-sonnet-4-5", + expected: "au.anthropic.claude-sonnet-4-5", + }, + { + region: "ap-southeast-4", + modelID: "anthropic.claude-haiku-v1:0", + expected: "au.anthropic.claude-haiku-v1:0", + }, + { region: "ap-southeast-2", modelID: "anthropic.claude-opus-4", expected: "apac.anthropic.claude-opus-4" }, + { + region: "ap-northeast-1", + modelID: "anthropic.claude-sonnet-4-5", + expected: "jp.anthropic.claude-sonnet-4-5", + }, + { region: "ap-northeast-1", modelID: "amazon.nova-pro-v1:0", expected: "jp.amazon.nova-pro-v1:0" }, + { region: "ap-south-1", modelID: "anthropic.claude-sonnet-4-5", expected: "apac.anthropic.claude-sonnet-4-5" }, + { region: "ap-south-1", modelID: "amazon.nova-lite-v1:0", expected: "apac.amazon.nova-lite-v1:0" }, + { region: "ca-central-1", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" }, + { + region: "us-east-1", + modelID: "global.anthropic.claude-sonnet-4-5", + expected: "global.anthropic.claude-sonnet-4-5", + }, + { region: "us-east-1", modelID: "us.anthropic.claude-sonnet-4-5", expected: "us.anthropic.claude-sonnet-4-5" }, + { region: "eu-west-1", modelID: "eu.anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" }, + { + region: "ap-northeast-1", + modelID: "jp.anthropic.claude-sonnet-4-5", + expected: "jp.anthropic.claude-sonnet-4-5", + }, + { + region: "ap-south-1", + modelID: "apac.anthropic.claude-sonnet-4-5", + expected: "apac.anthropic.claude-sonnet-4-5", + }, + { + region: "ap-southeast-2", + modelID: "au.anthropic.claude-sonnet-4-5", + expected: "au.anthropic.claude-sonnet-4-5", + }, + ] + yield* plugin.add(AmazonBedrockPlugin) + for (const item of cases) { + yield* plugin.trigger( + "aisdk.language", + { + model: model("amazon-bedrock", item.modelID), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: { region: item.region }, + }, + {}, + ) + } + expect(calls).toEqual(cases.map((item) => `languageModel:${item.expected}`)) + }), + ) + + it.effect("ignores non-Bedrock providers for language selection", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AmazonBedrockPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: model("openai", "anthropic.claude-sonnet-4-5"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: { region: "eu-west-1" }, + }, + {}, + ) + expect(calls).toEqual([]) + expect(result.language).toBeUndefined() + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-anthropic.test.ts b/packages/core/test/v2/plugin/provider-anthropic.test.ts new file mode 100644 index 000000000..bbea4a372 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-anthropic.test.ts @@ -0,0 +1,91 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { AnthropicPlugin } from "@opencode-ai/core/plugin/provider/anthropic" +import { it, model, provider } from "./provider-helper" + +describe("AnthropicPlugin", () => { + it.effect("applies legacy beta headers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AnthropicPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("anthropic", { + options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + cancel: false, + }, + ) + expect(result.provider.options.headers["anthropic-beta"]).toBe( + "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14", + ) + expect(result.provider.options.headers.Existing).toBe("1") + }), + ) + + it.effect("ignores non-Anthropic providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AnthropicPlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("openai"), cancel: false }) + expect(result.provider.options.headers["anthropic-beta"]).toBeUndefined() + }), + ) + + it.effect("creates Anthropic SDKs with the model provider ID as the SDK name", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const providers: string[] = [] + yield* plugin.add(AnthropicPlugin) + yield* plugin.add({ + id: PluginV2.ID.make("anthropic-sdk-inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + providers.push(evt.sdk.languageModel("claude-sonnet-4-5").provider) + }), + }), + }) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-anthropic", "claude-sonnet-4-5"), + package: "@ai-sdk/anthropic", + options: { name: "custom-anthropic", apiKey: "test" }, + }, + {}, + ) + expect(providers).toEqual(["custom-anthropic"]) + }), + ) + + it.effect("uses the Anthropic provider ID as the SDK name for the bundled Anthropic provider", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const providers: string[] = [] + yield* plugin.add(AnthropicPlugin) + yield* plugin.add({ + id: PluginV2.ID.make("anthropic-sdk-inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + providers.push(evt.sdk.languageModel("claude-sonnet-4-5").provider) + }), + }), + }) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("anthropic", "claude-sonnet-4-5"), + package: "@ai-sdk/anthropic", + options: { name: "anthropic", apiKey: "test" }, + }, + {}, + ) + expect(providers).toEqual(["anthropic"]) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-azure-cognitive-services.test.ts b/packages/core/test/v2/plugin/provider-azure-cognitive-services.test.ts new file mode 100644 index 000000000..b835cbeef --- /dev/null +++ b/packages/core/test/v2/plugin/provider-azure-cognitive-services.test.ts @@ -0,0 +1,127 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { AzureCognitiveServicesPlugin } from "@opencode-ai/core/plugin/provider/azure" +import { fakeSelectorSdk, it, model, provider, withEnv } from "./provider-helper" + +describe("AzureCognitiveServicesPlugin", () => { + it.effect("maps the resource env var to the Azure SDK baseURL", () => + withEnv({ AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: "cognitive" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AzureCognitiveServicesPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { provider: provider("azure-cognitive-services"), cancel: false }, + ) + expect(result.provider.endpoint).toEqual({ + type: "aisdk", + package: "test-provider", + }) + expect(result.provider.options.aisdk.provider.baseURL).toBe( + "https://cognitive.cognitiveservices.azure.com/openai", + ) + expect(result.provider.options.aisdk.provider.resourceName).toBeUndefined() + }), + ), + ) + + it.effect("leaves baseURL unset without resource env and ignores other providers", () => + withEnv({ AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AzureCognitiveServicesPlugin) + const azure = yield* plugin.trigger( + "provider.update", + {}, + { provider: provider("azure-cognitive-services"), cancel: false }, + ) + const other = yield* plugin.trigger("provider.update", {}, { provider: provider("openai"), cancel: false }) + expect(azure.provider.options.aisdk.provider.baseURL).toBeUndefined() + expect(azure.provider.endpoint).toEqual({ type: "aisdk", package: "test-provider" }) + expect(other.provider.options.aisdk.provider.baseURL).toBeUndefined() + expect(other.provider.endpoint).toEqual({ type: "aisdk", package: "test-provider" }) + }), + ), + ) + + it.effect("selects chat only for completion URLs", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AzureCognitiveServicesPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("azure-cognitive-services", "deployment"), + sdk: fakeSelectorSdk(calls), + options: { useCompletionUrls: true }, + }, + {}, + ) + expect(calls).toEqual(["chat:deployment"]) + }), + ) + + it.effect("uses the legacy Azure selector order and provider guard", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AzureCognitiveServicesPlugin) + yield* plugin.trigger( + "aisdk.language", + { model: model("azure-cognitive-services", "deployment"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + const ignored = yield* plugin.trigger( + "aisdk.language", + { model: model("openai", "deployment"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + expect(calls).toEqual(["responses:deployment"]) + expect(ignored.language).toBeUndefined() + }), + ) + + it.effect("falls back from responses to messages, chat, then languageModel", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + const sdk = fakeSelectorSdk(calls) + yield* plugin.add(AzureCognitiveServicesPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("azure-cognitive-services", "messages-deployment"), + sdk: { messages: sdk.messages, chat: sdk.chat, languageModel: sdk.languageModel }, + options: {}, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { + model: model("azure-cognitive-services", "chat-deployment"), + sdk: { chat: sdk.chat, languageModel: sdk.languageModel }, + options: {}, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { + model: model("azure-cognitive-services", "language-deployment"), + sdk: { languageModel: sdk.languageModel }, + options: {}, + }, + {}, + ) + expect(calls).toEqual([ + "messages:messages-deployment", + "chat:chat-deployment", + "languageModel:language-deployment", + ]) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-azure.test.ts b/packages/core/test/v2/plugin/provider-azure.test.ts new file mode 100644 index 000000000..12d8363e7 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-azure.test.ts @@ -0,0 +1,245 @@ +import { describe, expect } from "bun:test" +import { Effect, Layer } from "effect" +import { AuthV2 } from "@opencode-ai/core/auth" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { AuthPlugin } from "@opencode-ai/core/plugin/auth" +import { AzurePlugin } from "@opencode-ai/core/plugin/provider/azure" +import { testEffect } from "../../lib/effect" +import { fakeSelectorSdk, it, model, npmLayer, provider, withEnv } from "./provider-helper" + +const itWithAuth = testEffect(Layer.mergeAll(PluginV2.defaultLayer, AuthV2.defaultLayer, npmLayer)) + +describe("AzurePlugin", () => { + it.effect("resolves resourceName from env", () => + withEnv({ AZURE_RESOURCE_NAME: "from-env" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AzurePlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("azure"), cancel: false }) + expect(result.provider.options.aisdk.provider.resourceName).toBe("from-env") + }), + ), + ) + + it.effect("keeps explicit resourceName over env and ignores other providers", () => + withEnv({ AZURE_RESOURCE_NAME: "from-env" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AzurePlugin) + const azure = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("azure", { + options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "from-config" }, request: {} } }, + }), + cancel: false, + }, + ) + const other = yield* plugin.trigger("provider.update", {}, { provider: provider("openai"), cancel: false }) + expect(azure.provider.options.aisdk.provider.resourceName).toBe("from-config") + expect(other.provider.options.aisdk.provider.resourceName).toBeUndefined() + }), + ), + ) + + itWithAuth.effect("prefers auth resourceName over env", () => + withEnv( + { + AZURE_RESOURCE_NAME: "from-env", + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const auth = yield* AuthV2.Service + yield* auth.create({ + serviceID: AuthV2.ServiceID.make("azure"), + credential: new AuthV2.ApiKeyCredential({ + type: "api", + key: "key", + metadata: { resourceName: "from-auth" }, + }), + active: true, + }) + yield* plugin.add({ + ...AuthPlugin, + effect: AuthPlugin.effect.pipe(Effect.provideService(AuthV2.Service, auth)), + }) + yield* plugin.add(AzurePlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("azure"), cancel: false }) + expect(result.provider.options.aisdk.provider.resourceName).toBe("from-auth") + }), + ), + ) + + it.effect("falls back to env when configured resourceName is blank", () => + withEnv({ AZURE_RESOURCE_NAME: "from-env" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AzurePlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("azure", { + options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "" }, request: {} } }, + }), + cancel: false, + }, + ) + expect(result.provider.options.aisdk.provider.resourceName).toBe("from-env") + }), + ), + ) + + it.effect("falls back to env when configured resourceName is whitespace", () => + withEnv({ AZURE_RESOURCE_NAME: "from-env" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AzurePlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("azure", { + options: { headers: {}, body: {}, aisdk: { provider: { resourceName: " " }, request: {} } }, + }), + cancel: false, + }, + ) + expect(result.provider.options.aisdk.provider.resourceName).toBe("from-env") + }), + ), + ) + + it.effect("allows configured baseURL without resourceName", () => + withEnv({ AZURE_RESOURCE_NAME: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AzurePlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("azure", "deployment"), + package: "@ai-sdk/azure", + options: { name: "azure", baseURL: "https://proxy.example.com/openai" }, + }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ), + ) + + it.effect("rejects missing resourceName when baseURL is not configured", () => + withEnv({ AZURE_RESOURCE_NAME: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(AzurePlugin) + const exit = yield* plugin + .trigger( + "aisdk.sdk", + { model: model("azure", "deployment"), package: "@ai-sdk/azure", options: { name: "azure" } }, + {}, + ) + .pipe(Effect.exit) + expect(exit._tag).toBe("Failure") + }), + ), + ) + + it.effect("selects chat only for completion URLs", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AzurePlugin) + yield* plugin.trigger( + "aisdk.language", + { model: model("azure", "deployment"), sdk: fakeSelectorSdk(calls), options: { useCompletionUrls: true } }, + {}, + ) + expect(calls).toEqual(["chat:deployment"]) + }), + ) + + it.effect("selects chat from per-call useCompletionUrls", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AzurePlugin) + yield* plugin.trigger( + "aisdk.language", + { model: model("azure", "deployment"), sdk: fakeSelectorSdk(calls), options: { useCompletionUrls: true } }, + {}, + ) + expect(calls).toEqual(["chat:deployment"]) + }), + ) + + it.effect("ignores model useCompletionUrls when per-call option is unset", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AzurePlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("azure", "deployment", { + options: { headers: {}, body: {}, aisdk: { provider: {}, request: { useCompletionUrls: true } } }, + }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + expect(calls).toEqual(["responses:deployment"]) + }), + ) + + it.effect("uses the legacy Azure selector order and provider guard", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(AzurePlugin) + yield* plugin.trigger( + "aisdk.language", + { model: model("azure", "deployment"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + const ignored = yield* plugin.trigger( + "aisdk.language", + { model: model("openai", "deployment"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + expect(calls).toEqual(["responses:deployment"]) + expect(ignored.language).toBeUndefined() + }), + ) + + it.effect("falls back through the legacy Azure selector order", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + const make = (method: string) => (id: string) => { + calls.push(`${method}:${id}`) + return { modelId: id, provider: method, specificationVersion: "v3" } + } + yield* plugin.add(AzurePlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("azure", "messages-deployment"), + sdk: { messages: make("messages"), chat: make("chat"), languageModel: make("languageModel") }, + options: {}, + }, + {}, + ) + yield* plugin.trigger( + "aisdk.language", + { model: model("azure", "language-deployment"), sdk: { languageModel: make("languageModel") }, options: {} }, + {}, + ) + expect(calls).toEqual(["messages:messages-deployment", "languageModel:language-deployment"]) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-cerebras.test.ts b/packages/core/test/v2/plugin/provider-cerebras.test.ts new file mode 100644 index 000000000..7270d5367 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-cerebras.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, mock } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { CerebrasPlugin } from "@opencode-ai/core/plugin/provider/cerebras" +import { it, model, provider } from "./provider-helper" + +const cerebrasOptions: Record[] = [] + +void mock.module("@ai-sdk/cerebras", () => ({ + createCerebras: (options: Record) => { + const snapshot = { ...options } + cerebrasOptions.push(snapshot) + return { + languageModel: (modelID: string) => ({ modelID, provider: snapshot.name, specificationVersion: "v3" }), + } + }, +})) + +describe("CerebrasPlugin", () => { + it.effect("applies the legacy integration header", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CerebrasPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("cerebras", { + options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + cancel: false, + }, + ) + expect(result.provider.options.headers).toEqual({ Existing: "1", "X-Cerebras-3rd-Party-Integration": "opencode" }) + }), + ) + + it.effect("ignores non-Cerebras providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CerebrasPlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("groq"), cancel: false }) + expect(result.provider.options.headers).toEqual({}) + }), + ) + + it.effect("creates a bundled Cerebras SDK with the model provider ID as the SDK name", () => + Effect.gen(function* () { + cerebrasOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(CerebrasPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-cerebras", "llama-4-scout-17b-16e-instruct"), + package: "@ai-sdk/cerebras", + options: { name: "custom-cerebras", apiKey: "test" }, + }, + {}, + ) + expect(cerebrasOptions).toEqual([{ name: "custom-cerebras", apiKey: "test" }]) + expect(result.sdk.languageModel("llama-4-scout-17b-16e-instruct").provider).toBe("custom-cerebras") + }), + ) + + it.effect("preserves an explicit bundled Cerebras SDK name option", () => + Effect.gen(function* () { + cerebrasOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(CerebrasPlugin) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-cerebras", "llama-4-scout-17b-16e-instruct"), + package: "@ai-sdk/cerebras", + options: { name: "configured-cerebras", apiKey: "test" }, + }, + {}, + ) + expect(cerebrasOptions).toEqual([{ name: "configured-cerebras", apiKey: "test" }]) + }), + ) + + it.effect("ignores non-Cerebras SDK packages", () => + Effect.gen(function* () { + cerebrasOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(CerebrasPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-cerebras", "llama-4-scout-17b-16e-instruct"), + package: "@ai-sdk/groq", + options: { name: "custom-cerebras", apiKey: "test" }, + }, + {}, + ) + expect(cerebrasOptions).toEqual([]) + expect(result.sdk).toBeUndefined() + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-cloudflare-ai-gateway.test.ts b/packages/core/test/v2/plugin/provider-cloudflare-ai-gateway.test.ts new file mode 100644 index 000000000..72ad5da33 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-cloudflare-ai-gateway.test.ts @@ -0,0 +1,384 @@ +import { describe, expect, mock } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { CloudflareAIGatewayPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-ai-gateway" +import { it, model, withEnv } from "./provider-helper" + +const aiGatewayCalls: Record[] = [] +const unifiedCalls: string[] = [] +const gatewayModelCalls: unknown[] = [] + +function captureAiGatewayOptions(options: Record) { + const nested = + options.options && typeof options.options === "object" ? (options.options as Record) : undefined + return { + ...options, + ...(nested + ? { + options: { + ...nested, + headers: + nested.headers && typeof nested.headers === "object" + ? { ...(nested.headers as Record) } + : nested.headers, + }, + } + : {}), + } +} + +function resetCalls() { + aiGatewayCalls.length = 0 + unifiedCalls.length = 0 + gatewayModelCalls.length = 0 +} + +function cloudflareEnv(overrides: Record = {}) { + return { + CLOUDFLARE_ACCOUNT_ID: "env-account", + CLOUDFLARE_GATEWAY_ID: "env-gateway", + CLOUDFLARE_API_TOKEN: "env-token", + CF_AIG_TOKEN: undefined, + ...overrides, + } +} + +mock.module("ai-gateway-provider", () => ({ + createAiGateway(options: Record) { + aiGatewayCalls.push(captureAiGatewayOptions(options)) + return (input: unknown) => { + gatewayModelCalls.push(input) + return { + modelId: input, + provider: "cloudflare-ai-gateway", + specificationVersion: "v3", + } + } + }, +})) + +mock.module("ai-gateway-provider/providers/unified", () => ({ + createUnified() { + return (modelID: string) => { + unifiedCalls.push(modelID) + return { unifiedModelID: modelID } + } + }, +})) + +describe("CloudflareAIGatewayPlugin", () => { + it.effect("requires account, gateway, and token before creating the unified SDK", () => + withEnv( + { + CLOUDFLARE_ACCOUNT_ID: "acct", + CLOUDFLARE_GATEWAY_ID: "gateway", + CLOUDFLARE_API_TOKEN: "token", + CF_AIG_TOKEN: undefined, + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { name: "cloudflare-ai-gateway" }, + }, + {}, + ) + expect(result.sdk.languageModel("openai/gpt-5")).toBeDefined() + }), + ), + ) + + it.effect("passes legacy metadata, cache, log, and User-Agent values under the AI Gateway options key", () => + withEnv(cloudflareEnv(), () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { + name: "cloudflare-ai-gateway", + metadata: { invoked_by: "test", project: "opencode" }, + cacheTtl: 300, + cacheKey: "cache-key", + skipCache: true, + collectLog: false, + }, + }, + {}, + ) + + expect(aiGatewayCalls).toHaveLength(1) + expect(aiGatewayCalls[0]).toEqual({ + accountId: "env-account", + gateway: "env-gateway", + apiKey: "env-token", + options: { + metadata: { invoked_by: "test", project: "opencode" }, + cacheTtl: 300, + cacheKey: "cache-key", + skipCache: true, + collectLog: false, + headers: { + "User-Agent": expect.stringContaining("opencode/"), + }, + }, + }) + }), + ), + ) + + it.effect("parses legacy cf-aig-metadata header when metadata option is absent", () => + withEnv(cloudflareEnv(), () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { + name: "cloudflare-ai-gateway", + headers: { + "cf-aig-metadata": JSON.stringify({ invoked_by: "header", project: "opencode" }), + }, + }, + }, + {}, + ) + + expect(aiGatewayCalls[0]?.options).toMatchObject({ + metadata: { invoked_by: "header", project: "opencode" }, + }) + }), + ), + ) + + it.effect("prefers Cloudflare env values over auth/config-derived options", () => + withEnv(cloudflareEnv(), () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { + name: "cloudflare-ai-gateway", + accountId: "auth-account", + gateway: "auth-gateway", + apiKey: "auth-token", + }, + }, + {}, + ) + + expect(aiGatewayCalls[0]).toMatchObject({ + accountId: "env-account", + gateway: "env-gateway", + apiKey: "env-token", + }) + }), + ), + ) + + it.effect("accepts gatewayId metadata copied from auth into provider options", () => + withEnv( + cloudflareEnv({ + CLOUDFLARE_ACCOUNT_ID: undefined, + CLOUDFLARE_GATEWAY_ID: undefined, + CLOUDFLARE_API_TOKEN: undefined, + }), + () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { + name: "cloudflare-ai-gateway", + accountId: "auth-account", + gatewayId: "auth-gateway", + apiKey: "auth-token", + }, + }, + {}, + ) + + expect(aiGatewayCalls[0]).toMatchObject({ + accountId: "auth-account", + gateway: "auth-gateway", + apiKey: "auth-token", + }) + }), + ), + ) + + it.effect("falls back to CF_AIG_TOKEN when CLOUDFLARE_API_TOKEN is unset", () => + withEnv(cloudflareEnv({ CLOUDFLARE_API_TOKEN: undefined, CF_AIG_TOKEN: "cf-aig-token" }), () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { name: "cloudflare-ai-gateway" }, + }, + {}, + ) + + expect(aiGatewayCalls[0]).toMatchObject({ apiKey: "cf-aig-token" }) + }), + ), + ) + + it.effect("does not create an SDK when account and gateway IDs are missing", () => + withEnv(cloudflareEnv({ CLOUDFLARE_ACCOUNT_ID: undefined, CLOUDFLARE_GATEWAY_ID: undefined }), () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { name: "cloudflare-ai-gateway" }, + }, + {}, + ) + + expect(result.sdk).toBeUndefined() + expect(aiGatewayCalls).toHaveLength(0) + }), + ), + ) + + it.effect("does not create an SDK when the token is missing", () => + withEnv(cloudflareEnv({ CLOUDFLARE_API_TOKEN: undefined, CF_AIG_TOKEN: undefined }), () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { name: "cloudflare-ai-gateway" }, + }, + {}, + ) + + expect(result.sdk).toBeUndefined() + expect(aiGatewayCalls).toHaveLength(0) + }), + ), + ) + + it.effect("does not replace a configured baseURL with the Cloudflare AI Gateway SDK", () => + withEnv( + cloudflareEnv({ + CLOUDFLARE_ACCOUNT_ID: undefined, + CLOUDFLARE_GATEWAY_ID: undefined, + CLOUDFLARE_API_TOKEN: undefined, + }), + () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "ai-gateway-provider", + options: { name: "cloudflare-ai-gateway", baseURL: "https://proxy.example/v1" }, + }, + {}, + ) + + expect(result.sdk).toBeUndefined() + expect(aiGatewayCalls).toHaveLength(0) + }), + ), + ) + + it.effect("maps provider/model IDs through the unified Cloudflare provider unchanged", () => + withEnv(cloudflareEnv(), () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "anthropic/claude-sonnet-4-5"), + package: "ai-gateway-provider", + options: { name: "cloudflare-ai-gateway" }, + }, + {}, + ) + + expect(result.sdk.languageModel("anthropic/claude-sonnet-4-5")).toEqual({ + modelId: { unifiedModelID: "anthropic/claude-sonnet-4-5" }, + provider: "cloudflare-ai-gateway", + specificationVersion: "v3", + }) + expect(unifiedCalls).toEqual(["anthropic/claude-sonnet-4-5"]) + expect(gatewayModelCalls).toEqual([{ unifiedModelID: "anthropic/claude-sonnet-4-5" }]) + }), + ), + ) + + it.effect("ignores non Cloudflare AI Gateway packages", () => + withEnv(cloudflareEnv(), () => + Effect.gen(function* () { + resetCalls() + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareAIGatewayPlugin) + + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-ai-gateway", "openai/gpt-5"), + package: "@ai-sdk/openai-compatible", + options: { name: "cloudflare-ai-gateway" }, + }, + {}, + ) + + expect(result.sdk).toBeUndefined() + expect(aiGatewayCalls).toHaveLength(0) + }), + ), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-cloudflare-workers-ai.test.ts b/packages/core/test/v2/plugin/provider-cloudflare-workers-ai.test.ts new file mode 100644 index 000000000..3aed2a17b --- /dev/null +++ b/packages/core/test/v2/plugin/provider-cloudflare-workers-ai.test.ts @@ -0,0 +1,267 @@ +import { describe, expect } from "bun:test" +import { Effect, Layer } from "effect" +import { AuthV2 } from "@opencode-ai/core/auth" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { AuthPlugin } from "@opencode-ai/core/plugin/auth" +import { CloudflareWorkersAIPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-workers-ai" +import { testEffect } from "../../lib/effect" +import { fakeSelectorSdk, it, model, npmLayer, provider, withEnv } from "./provider-helper" + +const itWithAuth = testEffect(Layer.mergeAll(PluginV2.defaultLayer, AuthV2.defaultLayer, npmLayer)) + +function cloudflareLanguage(sdk: unknown, modelID = "@cf/model") { + return (sdk as { languageModel: (id: string) => { config: CloudflareConfig; provider: string } }).languageModel( + modelID, + ) +} + +type CloudflareConfig = { + url: (input: { path: string; modelId: string }) => string + headers: () => Record | Promise> +} + +function cloudflareURL(sdk: unknown, modelID = "@cf/model") { + return cloudflareLanguage(sdk, modelID).config.url({ path: "/chat/completions", modelId: modelID }) +} + +function cloudflareHeaders(sdk: unknown, modelID = "@cf/model") { + return cloudflareLanguage(sdk, modelID).config.headers() +} + +describe("CloudflareWorkersAIPlugin", () => { + it.effect("maps account ID to endpoint URL and creates an OpenAI-compatible SDK", () => + withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareWorkersAIPlugin) + const updated = yield* plugin.trigger( + "provider.update", + {}, + { provider: provider("cloudflare-workers-ai"), cancel: false }, + ) + const sdk = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-workers-ai", "@cf/model", { endpoint: updated.provider.endpoint }), + package: "@ai-sdk/openai-compatible", + options: { name: "cloudflare-workers-ai", headers: { custom: "header" } }, + }, + {}, + ) + expect(updated.provider.endpoint).toEqual({ + type: "aisdk", + package: "test-provider", + url: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1", + }) + expect(sdk.sdk).toBeDefined() + }), + ), + ) + + it.effect("preserves a configured endpoint URL instead of deriving one from account ID", () => + withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareWorkersAIPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("cloudflare-workers-ai", { + endpoint: { type: "aisdk", package: "test-provider", url: "https://proxy.example/v1" }, + }), + cancel: false, + }, + ) + expect(result.provider.endpoint).toEqual({ + type: "aisdk", + package: "test-provider", + url: "https://proxy.example/v1", + }) + }), + ), + ) + + it.effect("allows a configured baseURL without account ID", () => + withEnv({ CLOUDFLARE_ACCOUNT_ID: undefined, CLOUDFLARE_API_KEY: "key" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareWorkersAIPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-workers-ai", "@cf/model", { + endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" }, + }), + package: "@ai-sdk/openai-compatible", + options: { name: "cloudflare-workers-ai", baseURL: "https://proxy.example/v1" }, + }, + {}, + ) + expect(cloudflareURL(result.sdk)).toBe("https://proxy.example/v1/chat/completions") + }), + ), + ) + + itWithAuth.effect("falls back to auth account metadata when account env is absent", () => + withEnv( + { + CLOUDFLARE_ACCOUNT_ID: undefined, + CLOUDFLARE_API_KEY: undefined, + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const auth = yield* AuthV2.Service + yield* auth.create({ + serviceID: AuthV2.ServiceID.make("cloudflare-workers-ai"), + credential: new AuthV2.ApiKeyCredential({ + type: "api", + key: "auth-key", + metadata: { accountId: "auth-acct" }, + }), + active: true, + }) + yield* plugin.add({ + ...AuthPlugin, + effect: AuthPlugin.effect.pipe(Effect.provideService(AuthV2.Service, auth)), + }) + yield* plugin.add(CloudflareWorkersAIPlugin) + const updated = yield* plugin.trigger( + "provider.update", + {}, + { provider: provider("cloudflare-workers-ai"), cancel: false }, + ) + expect(updated.provider.endpoint).toEqual({ + type: "aisdk", + package: "test-provider", + url: "https://api.cloudflare.com/client/v4/accounts/auth-acct/ai/v1", + }) + }), + ), + ) + + it.effect("uses env account ID over configured account ID", () => + withEnv({ CLOUDFLARE_ACCOUNT_ID: "env-acct" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareWorkersAIPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("cloudflare-workers-ai", { + options: { headers: {}, body: {}, aisdk: { provider: { accountId: "configured-acct" }, request: {} } }, + }), + cancel: false, + }, + ) + expect(result.provider.endpoint).toEqual({ + type: "aisdk", + package: "test-provider", + url: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1", + }) + }), + ), + ) + + it.effect("uses env API key over auth or configured API key and keeps the Cloudflare User-Agent", () => + withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "env-key" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareWorkersAIPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-workers-ai", "@cf/model", { + endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" }, + }), + package: "@ai-sdk/openai-compatible", + options: { + name: "cloudflare-workers-ai", + apiKey: "auth-key", + baseURL: "https://proxy.example/v1", + headers: { custom: "header" }, + }, + }, + {}, + ) + const headers = yield* Effect.promise(() => Promise.resolve(cloudflareHeaders(result.sdk))) + expect(headers.authorization).toBe("Bearer env-key") + expect(headers.custom).toBe("header") + expect(headers["user-agent"]).toMatch(/^opencode\/.* cloudflare-workers-ai \(.+\) ai-sdk\/openai-compatible\//) + }), + ), + ) + + it.effect("expands account ID vars in endpoint URLs", () => + withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareWorkersAIPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-workers-ai", "@cf/model", { + endpoint: { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1", + }, + }), + package: "@ai-sdk/openai-compatible", + options: { + name: "cloudflare-workers-ai", + baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1", + }, + }, + {}, + ) + expect(cloudflareURL(result.sdk)).toBe( + "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1/chat/completions", + ) + }), + ), + ) + + it.effect("selects languageModel with the API model ID", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(CloudflareWorkersAIPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: model("cloudflare-workers-ai", "alias", { apiID: ModelV2.ID.make("@cf/api-model") }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + expect(result.language).toBeDefined() + expect(calls).toEqual(["languageModel:@cf/api-model"]) + }), + ) + + it.effect("does not create an SDK for non OpenAI-compatible packages", () => + withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CloudflareWorkersAIPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-workers-ai", "@cf/model", { + endpoint: { type: "aisdk", package: "@ai-sdk/anthropic", url: "https://proxy.example/v1" }, + }), + package: "@ai-sdk/anthropic", + options: { name: "cloudflare-workers-ai" }, + }, + {}, + ) + expect(result.sdk).toBeUndefined() + }), + ), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-cohere.test.ts b/packages/core/test/v2/plugin/provider-cohere.test.ts new file mode 100644 index 000000000..54bec2cec --- /dev/null +++ b/packages/core/test/v2/plugin/provider-cohere.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, mock } from "bun:test" +import { Effect } from "effect" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { CoherePlugin } from "@opencode-ai/core/plugin/provider/cohere" +import { fakeSelectorSdk, it, model } from "./provider-helper" + +const cohereOptions: Record[] = [] + +void mock.module("@ai-sdk/cohere", () => ({ + createCohere: (options: Record) => { + cohereOptions.push({ ...options }) + return { + languageModel: (modelID: string) => ({ + modelID, + provider: `${options.name ?? "cohere"}.chat`, + specificationVersion: "v3", + }), + } + }, +})) + +describe("CoherePlugin", () => { + it.effect("creates a Cohere SDK only for @ai-sdk/cohere", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CoherePlugin) + + const ignored = yield* plugin.trigger( + "aisdk.sdk", + { model: model("cohere", "command"), package: "@ai-sdk/openai-compatible", options: { name: "cohere" } }, + {}, + ) + expect(ignored.sdk).toBeUndefined() + + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("cohere", "command"), package: "@ai-sdk/cohere", options: { name: "cohere" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("uses the model provider ID as the bundled SDK name", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(CoherePlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-cohere", "command-r-plus"), + package: "@ai-sdk/cohere", + options: { name: "custom-cohere", apiKey: "test", baseURL: "https://cohere.example" }, + }, + {}, + ) + + expect(cohereOptions.at(-1)).toEqual({ + name: "custom-cohere", + apiKey: "test", + baseURL: "https://cohere.example", + }) + expect(result.sdk?.languageModel("command-r-plus").provider).toBe("custom-cohere.chat") + }), + ) + + it.effect("leaves language selection to the default languageModel fallback", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + const sdk = fakeSelectorSdk(calls) + yield* plugin.add(CoherePlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { model: model("cohere", "alias", { apiID: ModelV2.ID.make("command-r-plus") }), sdk, options: {} }, + {}, + ) + + expect(result.language).toBeUndefined() + expect(calls).toEqual([]) + expect(result.language ?? sdk.languageModel("command-r-plus")).toBeDefined() + expect(calls).toEqual(["languageModel:command-r-plus"]) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-deepinfra.test.ts b/packages/core/test/v2/plugin/provider-deepinfra.test.ts new file mode 100644 index 000000000..1195b8c18 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-deepinfra.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, mock } from "bun:test" +import { Effect, Layer } from "effect" +import { AISDK } from "@opencode-ai/core/aisdk" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { DeepInfraPlugin } from "@opencode-ai/core/plugin/provider/deepinfra" +import { testEffect } from "../../lib/effect" +import { it, model } from "./provider-helper" + +const itAISDK = testEffect(Layer.provideMerge(AISDK.layer, PluginV2.defaultLayer)) +const deepinfraOptions: Record[] = [] +const deepinfraLanguageModels: string[] = [] + +void mock.module("@ai-sdk/deepinfra", () => ({ + createDeepInfra: (options: Record) => { + const captured = { ...options } + deepinfraOptions.push(captured) + return { + languageModel: (modelID: string) => { + deepinfraLanguageModels.push(modelID) + return { modelID, provider: `${captured.name ?? "deepinfra"}.chat`, specificationVersion: "v3" } + }, + } + }, +})) + +function resetDeepInfraMock() { + deepinfraOptions.length = 0 + deepinfraLanguageModels.length = 0 +} + +describe("DeepInfraPlugin", () => { + it.effect("creates a DeepInfra SDK for @ai-sdk/deepinfra", () => + Effect.gen(function* () { + resetDeepInfraMock() + const plugin = yield* PluginV2.Service + yield* plugin.add(DeepInfraPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("deepinfra", "model"), package: "@ai-sdk/deepinfra", options: { name: "deepinfra" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("passes the model provider ID as the bundled DeepInfra SDK name", () => + Effect.gen(function* () { + resetDeepInfraMock() + const plugin = yield* PluginV2.Service + yield* plugin.add(DeepInfraPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-deepinfra", "model"), + package: "@ai-sdk/deepinfra", + options: { name: "custom-deepinfra", apiKey: "test" }, + }, + {}, + ) + expect(result.sdk.languageModel("model").provider).toBe("custom-deepinfra.chat") + expect(deepinfraOptions).toEqual([{ name: "custom-deepinfra", apiKey: "test" }]) + }), + ) + + it.effect("uses the canonical provider ID as the bundled DeepInfra SDK name", () => + Effect.gen(function* () { + resetDeepInfraMock() + const plugin = yield* PluginV2.Service + yield* plugin.add(DeepInfraPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("deepinfra", "model"), + package: "@ai-sdk/deepinfra", + options: { name: "deepinfra", apiKey: "test" }, + }, + {}, + ) + expect(result.sdk.languageModel("model").provider).toBe("deepinfra.chat") + expect(deepinfraOptions).toEqual([{ name: "deepinfra", apiKey: "test" }]) + }), + ) + + it.effect("matches only the exact bundled DeepInfra package", () => + Effect.gen(function* () { + resetDeepInfraMock() + const plugin = yield* PluginV2.Service + yield* plugin.add(DeepInfraPlugin) + const packages = [ + "unmatched-package", + "@ai-sdk/deepinfra-compatible", + "file:///tmp/@ai-sdk/deepinfra-provider.js", + ] + yield* Effect.forEach(packages, (item) => + Effect.gen(function* () { + const ignored = yield* plugin.trigger( + "aisdk.sdk", + { model: model("deepinfra", "model"), package: item, options: { name: "deepinfra" } }, + {}, + ) + expect(ignored.sdk).toBeUndefined() + }), + ) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("deepinfra", "model"), package: "@ai-sdk/deepinfra", options: { name: "deepinfra" } }, + {}, + ) + expect(result.sdk).toBeDefined() + expect(deepinfraOptions).toEqual([{ name: "deepinfra" }]) + }), + ) + + itAISDK.effect("uses the default languageModel selection for DeepInfra models", () => + Effect.gen(function* () { + resetDeepInfraMock() + const plugin = yield* PluginV2.Service + const aisdk = yield* AISDK.Service + yield* plugin.add(DeepInfraPlugin) + const language = yield* aisdk.language( + model("deepinfra", "meta-llama/Llama-3.3-70B-Instruct", { + endpoint: { type: "aisdk", package: "@ai-sdk/deepinfra" }, + }), + ) + expect(language.provider).toBe("deepinfra.chat") + expect(deepinfraLanguageModels).toEqual(["meta-llama/Llama-3.3-70B-Instruct"]) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-dynamic.test.ts b/packages/core/test/v2/plugin/provider-dynamic.test.ts new file mode 100644 index 000000000..cca331b11 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-dynamic.test.ts @@ -0,0 +1,172 @@ +import { Npm } from "@opencode-ai/core/npm" +import { describe, expect } from "bun:test" +import { Cause, Effect, Layer, Option } from "effect" +import fs from "fs/promises" +import os from "os" +import path from "path" +import { fileURLToPath } from "url" +import { AISDK } from "@opencode-ai/core/aisdk" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { DynamicProviderPlugin } from "@opencode-ai/core/plugin/provider/dynamic" +import { testEffect } from "../../lib/effect" +import { fixtureProvider, it, model, npmLayer } from "./provider-helper" + +const fixtureProviderPath = fileURLToPath(fixtureProvider) +const itWithAISDK = testEffect(AISDK.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer))) + +function npmEntrypointLayer(entrypoint: Option.Option) { + return Layer.succeed( + Npm.Service, + Npm.Service.of({ + add: () => Effect.succeed({ directory: "", entrypoint }), + install: () => Effect.void, + which: () => Effect.succeed(Option.none()), + }), + ) +} + +function dynamicPlugin(layer = npmLayer) { + return { id: DynamicProviderPlugin.id, effect: DynamicProviderPlugin.effect.pipe(Effect.provide(layer)) } +} + +function tempEntrypoint(source: string) { + return Effect.acquireRelease( + Effect.promise(async () => { + const directory = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-provider-dynamic-")) + const entrypoint = path.join(directory, "provider.mjs") + await Bun.write(entrypoint, source) + return { directory, entrypoint } + }), + (tmp) => Effect.promise(() => fs.rm(tmp.directory, { recursive: true, force: true })), + ) +} + +describe("DynamicProviderPlugin", () => { + it.effect("creates an SDK from a provider factory export", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(dynamicPlugin()) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom", "test-model"), + package: fixtureProvider, + options: { name: "custom", marker: "dynamic" }, + }, + {}, + ) + expect(result.sdk.options).toEqual({ marker: "dynamic", name: "custom" }) + expect(result.sdk.languageModel("x")).toEqual({ modelID: "x", options: { marker: "dynamic", name: "custom" } }) + }), + ) + + it.effect("does not override an SDK already supplied by an earlier plugin", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const sdk = { marker: "existing" } + yield* plugin.add(dynamicPlugin()) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom", "test-model"), + package: fixtureProvider, + options: { name: "custom", marker: "dynamic" }, + }, + { sdk }, + ) + expect(result.sdk).toBe(sdk) + }), + ) + + it.effect("injects the provider ID as the SDK factory name", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(dynamicPlugin()) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-provider", "test-model"), + package: fixtureProvider, + options: { name: "custom-provider", marker: "dynamic" }, + }, + {}, + ) + expect(result.sdk.options).toEqual({ marker: "dynamic", name: "custom-provider" }) + }), + ) + + it.effect("loads npm packages through their resolved import entrypoint", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.some(fixtureProviderPath)))) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("npm-provider", "test-model"), + package: "fixture-provider", + options: { name: "npm-provider", marker: "npm" }, + }, + {}, + ) + expect(result.sdk.languageModel("x")).toEqual({ modelID: "x", options: { marker: "npm", name: "npm-provider" } }) + }), + ) + + itWithAISDK.effect("wraps missing npm entrypoint failures as AISDK init errors", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const aisdk = yield* AISDK.Service + yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.none()))) + const exit = yield* aisdk + .language(model("missing-entrypoint", "alias", { endpoint: { type: "aisdk", package: "fixture-provider" } })) + .pipe(Effect.exit) + expect(exit._tag).toBe("Failure") + if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError") + }), + ) + + itWithAISDK.effect("wraps dynamic import failures as AISDK init errors", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const aisdk = yield* AISDK.Service + yield* plugin.add(dynamicPlugin()) + const exit = yield* aisdk + .language( + model("bad-import", "alias", { endpoint: { type: "aisdk", package: "file:///missing/provider-factory.js" } }), + ) + .pipe(Effect.exit) + expect(exit._tag).toBe("Failure") + if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError") + }), + ) + + itWithAISDK.live("wraps missing provider factory exports as AISDK init errors", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const aisdk = yield* AISDK.Service + const tmp = yield* tempEntrypoint("export const notAProviderFactory = true\n") + yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.some(tmp.entrypoint)))) + const exit = yield* aisdk + .language(model("missing-factory", "alias", { endpoint: { type: "aisdk", package: "fixture-provider" } })) + .pipe(Effect.exit) + expect(exit._tag).toBe("Failure") + if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError") + }), + ) + + itWithAISDK.effect("uses the model apiID for the default language model", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const aisdk = yield* AISDK.Service + yield* plugin.add(dynamicPlugin()) + const language = yield* aisdk.language( + model("custom", "alias", { + apiID: ModelV2.ID.make("test-model-api"), + endpoint: { type: "aisdk", package: fixtureProvider }, + }), + ) + expect(language).toMatchObject({ modelID: "test-model-api", options: { name: "custom" } }) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-gateway.test.ts b/packages/core/test/v2/plugin/provider-gateway.test.ts new file mode 100644 index 000000000..8ee69b7dd --- /dev/null +++ b/packages/core/test/v2/plugin/provider-gateway.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, mock } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { GatewayPlugin } from "@opencode-ai/core/plugin/provider/gateway" +import { it, model } from "./provider-helper" + +const gatewayCalls: Record[] = [] +const vercelGatewayModels = ["anthropic/claude-sonnet-4", "openai/gpt-5", "google/gemini-2.5-pro"] + +mock.module("@ai-sdk/gateway", () => ({ + createGateway(options: Record) { + gatewayCalls.push({ ...options }) + return { + languageModel(modelID: string) { + return { + modelId: modelID, + provider: options.name, + specificationVersion: "v3", + } + }, + } + }, +})) + +describe("GatewayPlugin", () => { + it.effect("creates a Gateway SDK for @ai-sdk/gateway", () => + Effect.gen(function* () { + gatewayCalls.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GatewayPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("gateway", "model"), package: "@ai-sdk/gateway", options: { name: "gateway" } }, + {}, + ) + expect(result.sdk).toBeDefined() + expect(gatewayCalls).toHaveLength(1) + }), + ) + + it.effect("passes the model providerID as the Gateway SDK name", () => + Effect.gen(function* () { + gatewayCalls.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GatewayPlugin) + + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("vercel", "anthropic/claude-sonnet-4"), + package: "@ai-sdk/gateway", + options: { name: "vercel", apiKey: "test-key" }, + }, + {}, + ) + + expect(gatewayCalls).toEqual([{ name: "vercel", apiKey: "test-key" }]) + expect(result.sdk.languageModel("anthropic/claude-sonnet-4").provider).toBe("vercel") + }), + ) + + it.effect("matches Vercel AI Gateway models by their @ai-sdk/gateway package", () => + Effect.gen(function* () { + gatewayCalls.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GatewayPlugin) + + for (const modelID of vercelGatewayModels) { + const ignored = yield* plugin.trigger( + "aisdk.sdk", + { model: model("vercel", modelID), package: "@ai-sdk/vercel", options: { name: "vercel" } }, + {}, + ) + expect(ignored.sdk).toBeUndefined() + + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("vercel", modelID), package: "@ai-sdk/gateway", options: { name: "vercel" } }, + {}, + ) + expect(result.sdk).toBeDefined() + } + + expect(gatewayCalls).toHaveLength(3) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-gitlab.test.ts b/packages/core/test/v2/plugin/provider-gitlab.test.ts new file mode 100644 index 000000000..0b71310e0 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-gitlab.test.ts @@ -0,0 +1,346 @@ +import { describe, expect, mock } from "bun:test" +import { Effect, Layer } from "effect" +import { AuthV2 } from "@opencode-ai/core/auth" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { AuthPlugin } from "@opencode-ai/core/plugin/auth" +import { GitLabPlugin } from "@opencode-ai/core/plugin/provider/gitlab" +import { testEffect } from "../../lib/effect" +import { it, model, npmLayer, provider, withEnv } from "./provider-helper" + +const gitlabSDKOptions: Record[] = [] + +void mock.module("gitlab-ai-provider", () => ({ + VERSION: "test-version", + createGitLab: (options: Record) => { + gitlabSDKOptions.push(options) + return { + agenticChat: (id: string, options: unknown) => ({ id, options, type: "agentic" }), + workflowChat: (id: string, options: unknown) => ({ id, options, type: "workflow" }), + } + }, + discoverWorkflowModels: async () => ({ models: [], project: undefined }), + isWorkflowModel: (id: string) => id === "duo-workflow" || id === "duo-workflow-exact", +})) + +const itWithAuth = testEffect(Layer.mergeAll(PluginV2.defaultLayer, AuthV2.defaultLayer, npmLayer)) + +describe("GitLabPlugin", () => { + it.effect("creates SDKs with legacy default instance URL, token env, headers, and feature flags", () => + withEnv( + { + GITLAB_INSTANCE_URL: undefined, + GITLAB_TOKEN: "env-token", + }, + () => + Effect.gen(function* () { + gitlabSDKOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GitLabPlugin) + yield* plugin.trigger( + "aisdk.sdk", + { model: model("gitlab", "claude"), package: "gitlab-ai-provider", options: { name: "gitlab" } }, + {}, + ) + expect(gitlabSDKOptions).toHaveLength(1) + expect(gitlabSDKOptions[0].instanceUrl).toBe("https://gitlab.com") + expect(gitlabSDKOptions[0].apiKey).toBe("env-token") + expect(gitlabSDKOptions[0].aiGatewayHeaders).toMatchObject({ + "anthropic-beta": "context-1m-2025-08-07", + }) + expect(String((gitlabSDKOptions[0].aiGatewayHeaders as Record)["User-Agent"])).toContain( + "gitlab-ai-provider/test-version", + ) + expect(gitlabSDKOptions[0].featureFlags).toEqual({ + duo_agent_platform_agentic_chat: true, + duo_agent_platform: true, + }) + }), + ), + ) + + it.effect("uses GITLAB_INSTANCE_URL when instanceUrl is not configured", () => + withEnv( + { + GITLAB_INSTANCE_URL: "https://env.gitlab.example", + GITLAB_TOKEN: undefined, + }, + () => + Effect.gen(function* () { + gitlabSDKOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GitLabPlugin) + yield* plugin.trigger( + "aisdk.sdk", + { model: model("gitlab", "claude"), package: "gitlab-ai-provider", options: { name: "gitlab" } }, + {}, + ) + expect(gitlabSDKOptions[0].instanceUrl).toBe("https://env.gitlab.example") + }), + ), + ) + + it.effect("keeps configured instance URL, apiKey, aiGatewayHeaders, and featureFlags over env/defaults", () => + withEnv( + { + GITLAB_INSTANCE_URL: "https://env.gitlab.example", + GITLAB_TOKEN: "env-token", + }, + () => + Effect.gen(function* () { + gitlabSDKOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GitLabPlugin) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("gitlab", "claude"), + package: "gitlab-ai-provider", + options: { + name: "gitlab", + instanceUrl: "https://configured.gitlab.example", + apiKey: "configured-token", + aiGatewayHeaders: { + "anthropic-beta": "configured-beta", + "x-gitlab-test": "1", + }, + featureFlags: { + duo_agent_platform: false, + custom_flag: true, + }, + }, + }, + {}, + ) + expect(gitlabSDKOptions[0].instanceUrl).toBe("https://configured.gitlab.example") + expect(gitlabSDKOptions[0].apiKey).toBe("configured-token") + expect(gitlabSDKOptions[0].aiGatewayHeaders).toMatchObject({ + "anthropic-beta": "configured-beta", + "x-gitlab-test": "1", + }) + expect(gitlabSDKOptions[0].featureFlags).toEqual({ + duo_agent_platform_agentic_chat: true, + duo_agent_platform: false, + custom_flag: true, + }) + }), + ), + ) + + it.effect("ignores non-GitLab SDK packages", () => + Effect.gen(function* () { + gitlabSDKOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GitLabPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("gitlab", "claude"), package: "@ai-sdk/openai", options: { name: "gitlab" } }, + {}, + ) + expect(result.sdk).toBeUndefined() + expect(gitlabSDKOptions).toHaveLength(0) + }), + ) + + itWithAuth.effect("uses active API auth token over GITLAB_TOKEN", () => + withEnv( + { + GITLAB_TOKEN: "env-token", + }, + () => + Effect.gen(function* () { + gitlabSDKOptions.length = 0 + const plugin = yield* PluginV2.Service + const auth = yield* AuthV2.Service + yield* auth.create({ + serviceID: AuthV2.ServiceID.make("gitlab"), + credential: new AuthV2.ApiKeyCredential({ type: "api", key: "auth-token" }), + active: true, + }) + yield* plugin.add({ + ...AuthPlugin, + effect: AuthPlugin.effect.pipe(Effect.provideService(AuthV2.Service, auth)), + }) + yield* plugin.add(GitLabPlugin) + const updated = yield* plugin.trigger("provider.update", {}, { provider: provider("gitlab"), cancel: false }) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("gitlab", "claude"), + package: "gitlab-ai-provider", + options: updated.provider.options.aisdk.provider, + }, + {}, + ) + expect(gitlabSDKOptions[0].apiKey).toBe("auth-token") + }), + ), + ) + + itWithAuth.effect("uses active OAuth access token when no API auth exists", () => + withEnv( + { + GITLAB_TOKEN: undefined, + }, + () => + Effect.gen(function* () { + gitlabSDKOptions.length = 0 + const plugin = yield* PluginV2.Service + const auth = yield* AuthV2.Service + yield* auth.create({ + serviceID: AuthV2.ServiceID.make("gitlab"), + credential: new AuthV2.OAuthCredential({ + type: "oauth", + refresh: "refresh-token", + access: "oauth-token", + expires: 9999999999999, + }), + active: true, + }) + yield* plugin.add({ + ...AuthPlugin, + effect: AuthPlugin.effect.pipe(Effect.provideService(AuthV2.Service, auth)), + }) + yield* plugin.add(GitLabPlugin) + const updated = yield* plugin.trigger("provider.update", {}, { provider: provider("gitlab"), cancel: false }) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("gitlab", "claude"), + package: "gitlab-ai-provider", + options: updated.provider.options.aisdk.provider, + }, + {}, + ) + expect(gitlabSDKOptions[0].apiKey).toBe("oauth-token") + }), + ), + ) + + it.effect("uses workflowChat for duo workflow models and preserves selectedModelRef", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: [string, unknown][] = [] + yield* plugin.add(GitLabPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: model("gitlab", "duo-workflow-custom", { + options: { + headers: {}, + body: {}, + aisdk: { provider: {}, request: { workflowRef: "ref", workflowDefinition: "definition" } }, + }, + }), + sdk: { + workflowChat: (id: string, options: unknown) => { + calls.push([id, options]) + return { id, options } + }, + agenticChat: () => undefined, + }, + options: { featureFlags: { configured: true } }, + }, + {}, + ) + expect(calls).toEqual([ + ["duo-workflow", { featureFlags: { configured: true }, workflowDefinition: "definition" }], + ]) + expect(result.language as unknown).toEqual({ + id: "duo-workflow", + options: calls[0]?.[1], + selectedModelRef: "ref", + }) + }), + ) + + it.effect("uses exact static workflow model ids when the provider recognizes them", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: [string, unknown][] = [] + yield* plugin.add(GitLabPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: model("gitlab", "duo-workflow-exact"), + sdk: { + workflowChat: (id: string, options: unknown) => { + calls.push([id, options]) + return { id, options } + }, + agenticChat: () => undefined, + }, + options: { featureFlags: { configured: true } }, + }, + {}, + ) + expect(calls).toEqual([ + ["duo-workflow-exact", { featureFlags: { configured: true }, workflowDefinition: undefined }], + ]) + expect(result.language as unknown).toEqual({ id: "duo-workflow-exact", options: calls[0]?.[1] }) + }), + ) + + it.effect("uses provider feature flags instead of request feature flags", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: [string, unknown][] = [] + yield* plugin.add(GitLabPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("gitlab", "duo-workflow-custom", { + options: { + headers: {}, + body: {}, + aisdk: { provider: {}, request: { featureFlags: { request_flag: true } } }, + }, + }), + sdk: { + workflowChat: (id: string, options: unknown) => { + calls.push([id, options]) + return { id, options } + }, + agenticChat: () => undefined, + }, + options: { featureFlags: { configured: true } }, + }, + {}, + ) + expect(calls).toEqual([["duo-workflow", { featureFlags: { configured: true }, workflowDefinition: undefined }]]) + }), + ) + + it.effect("uses agenticChat with provider aiGatewayHeaders and feature flags for normal models", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: [string, unknown][] = [] + yield* plugin.add(GitLabPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("gitlab", "claude", { + options: { headers: { h: "v" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + sdk: { + workflowChat: () => undefined, + agenticChat: (id: string, options: unknown) => { + const selected = options as { + aiGatewayHeaders?: Record + featureFlags?: Record + } + calls.push([ + id, + { aiGatewayHeaders: { ...selected.aiGatewayHeaders }, featureFlags: { ...selected.featureFlags } }, + ]) + }, + }, + options: { aiGatewayHeaders: { fallback: "header" }, featureFlags: { duo_agent_platform: true } }, + }, + {}, + ) + expect(calls).toEqual([ + ["claude", { aiGatewayHeaders: { fallback: "header" }, featureFlags: { duo_agent_platform: true } }], + ]) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-google-vertex-anthropic.test.ts b/packages/core/test/v2/plugin/provider-google-vertex-anthropic.test.ts new file mode 100644 index 000000000..6bcece53c --- /dev/null +++ b/packages/core/test/v2/plugin/provider-google-vertex-anthropic.test.ts @@ -0,0 +1,147 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { GoogleVertexAnthropicPlugin } from "@opencode-ai/core/plugin/provider/google-vertex" +import { fakeSelectorSdk, it, model, provider, withEnv } from "./provider-helper" + +describe("GoogleVertexAnthropicPlugin", () => { + it.effect("resolves legacy project and location env on provider update", () => + withEnv( + { + GOOGLE_CLOUD_PROJECT: "cloud-project", + GCP_PROJECT: "gcp-project", + GCLOUD_PROJECT: "gcloud-project", + GOOGLE_CLOUD_LOCATION: "cloud-location", + VERTEX_LOCATION: "vertex-location", + GOOGLE_VERTEX_LOCATION: "google-vertex-location", + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexAnthropicPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { provider: provider("google-vertex-anthropic"), cancel: false }, + ) + expect(result.provider.options.aisdk.provider.project).toBe("cloud-project") + expect(result.provider.options.aisdk.provider.location).toBe("cloud-location") + }), + ), + ) + + it.effect("keeps configured project and location over env fallback", () => + withEnv({ GOOGLE_CLOUD_PROJECT: "env-project", GOOGLE_CLOUD_LOCATION: "env-location" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexAnthropicPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("google-vertex-anthropic", { + options: { + headers: {}, + body: {}, + aisdk: { provider: { project: "configured-project", location: "configured-location" }, request: {} }, + }, + }), + cancel: false, + }, + ) + expect(result.provider.options.aisdk.provider.project).toBe("configured-project") + expect(result.provider.options.aisdk.provider.location).toBe("configured-location") + }), + ), + ) + + it.effect("creates SDKs from legacy env fallback and default location", () => + withEnv( + { + GOOGLE_CLOUD_PROJECT: undefined, + GCP_PROJECT: "gcp-project", + GCLOUD_PROJECT: "gcloud-project", + GOOGLE_CLOUD_LOCATION: undefined, + VERTEX_LOCATION: undefined, + GOOGLE_VERTEX_LOCATION: "ignored-location", + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexAnthropicPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("google-vertex-anthropic", "claude-sonnet-4-5"), + package: "@ai-sdk/google-vertex/anthropic", + options: { name: "google-vertex-anthropic" }, + }, + {}, + ) + expect(result.sdk.languageModel("claude-sonnet-4-5").config.baseURL).toBe( + "https://aiplatform.googleapis.com/v1/projects/gcp-project/locations/global/publishers/anthropic/models", + ) + }), + ), + ) + + it.effect("uses GOOGLE_CLOUD_LOCATION before VERTEX_LOCATION when creating SDKs", () => + withEnv( + { GOOGLE_CLOUD_PROJECT: "project", GOOGLE_CLOUD_LOCATION: "cloud-location", VERTEX_LOCATION: "vertex-location" }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexAnthropicPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("google-vertex-anthropic", "claude-sonnet-4-5"), + package: "@ai-sdk/google-vertex/anthropic", + options: { name: "google-vertex-anthropic" }, + }, + {}, + ) + expect(result.sdk.languageModel("claude-sonnet-4-5").config.baseURL).toBe( + "https://cloud-location-aiplatform.googleapis.com/v1/projects/project/locations/cloud-location/publishers/anthropic/models", + ) + }), + ), + ) + + it.effect("trims model IDs before selecting language models", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(GoogleVertexAnthropicPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("google-vertex-anthropic", " claude-sonnet-4-5 "), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: {}, + }, + {}, + ) + expect(calls).toEqual(["languageModel:claude-sonnet-4-5"]) + }), + ) + + it.effect("ignores non Vertex Anthropic providers for language selection", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(GoogleVertexAnthropicPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: model("google-vertex", "claude-sonnet-4-5"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: {}, + }, + {}, + ) + expect(calls).toEqual([]) + expect(result.language).toBeUndefined() + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-google-vertex.test.ts b/packages/core/test/v2/plugin/provider-google-vertex.test.ts new file mode 100644 index 000000000..3bd60fd72 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-google-vertex.test.ts @@ -0,0 +1,300 @@ +import { describe, expect, mock } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { GoogleVertexPlugin } from "@opencode-ai/core/plugin/provider/google-vertex" +import { fakeSelectorSdk, it, model, provider, withEnv } from "./provider-helper" + +const vertexOptions: Record[] = [] + +void mock.module("@ai-sdk/google-vertex", () => ({ + createVertex: (options: Record) => { + vertexOptions.push(options) + return { + languageModel: (modelID: string) => ({ modelID, provider: "google-vertex", specificationVersion: "v3" }), + } + }, +})) + +void mock.module("google-auth-library", () => ({ + GoogleAuth: class { + async getApplicationDefault() { + return { + credential: { + async getAccessToken() { + return { token: "vertex-token" } + }, + }, + } + } + }, +})) + +describe("GoogleVertexPlugin", () => { + it.effect("resolves project and location from env using legacy precedence", () => + withEnv( + { + GOOGLE_CLOUD_PROJECT: "google-cloud-project", + GCP_PROJECT: "gcp-project", + GCLOUD_PROJECT: "gcloud-project", + GOOGLE_VERTEX_LOCATION: "google-vertex-location", + GOOGLE_CLOUD_LOCATION: "google-cloud-location", + VERTEX_LOCATION: "vertex-location", + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("google-vertex", { + endpoint: { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", + }, + }), + cancel: false, + }, + ) + expect(result.provider.options.aisdk.provider.project).toBe("google-cloud-project") + expect(result.provider.options.aisdk.provider.location).toBe("google-vertex-location") + expect(result.provider.endpoint).toEqual({ + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://google-vertex-location-aiplatform.googleapis.com/v1/projects/google-cloud-project/locations/google-vertex-location", + }) + }), + ), + ) + + it.effect("resolves the advertised GOOGLE_VERTEX_PROJECT env for provider updates and SDKs", () => + withEnv( + { + GOOGLE_VERTEX_PROJECT: "vertex-project", + GOOGLE_CLOUD_PROJECT: undefined, + GCP_PROJECT: undefined, + GCLOUD_PROJECT: undefined, + GOOGLE_VERTEX_LOCATION: "europe-west4", + GOOGLE_CLOUD_LOCATION: undefined, + VERTEX_LOCATION: undefined, + }, + () => + Effect.gen(function* () { + vertexOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexPlugin) + const updated = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("google-vertex", { + endpoint: { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", + }, + }), + cancel: false, + }, + ) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("google-vertex", "gemini", { + endpoint: { type: "aisdk", package: "@ai-sdk/google-vertex" }, + }), + package: "@ai-sdk/google-vertex", + options: { name: "google-vertex" }, + }, + {}, + ) + + expect(updated.provider.options.aisdk.provider.project).toBe("vertex-project") + expect(updated.provider.endpoint).toEqual({ + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://europe-west4-aiplatform.googleapis.com/v1/projects/vertex-project/locations/europe-west4", + }) + expect(vertexOptions[0].project).toBe("vertex-project") + expect(vertexOptions[0].location).toBe("europe-west4") + }), + ), + ) + + it.effect("keeps configured project and location over env and uses global endpoint", () => + withEnv( + { + GOOGLE_CLOUD_PROJECT: "env-project", + GCP_PROJECT: "env-gcp-project", + GCLOUD_PROJECT: "env-gcloud-project", + GOOGLE_VERTEX_LOCATION: "env-location", + GOOGLE_CLOUD_LOCATION: "env-google-cloud-location", + VERTEX_LOCATION: "env-vertex-location", + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("google-vertex", { + endpoint: { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", + }, + options: { + headers: {}, + body: {}, + aisdk: { provider: { project: "config-project", location: "global" }, request: {} }, + }, + }), + cancel: false, + }, + ) + expect(result.provider.options.aisdk.provider.project).toBe("config-project") + expect(result.provider.options.aisdk.provider.location).toBe("global") + expect(result.provider.endpoint).toEqual({ + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://aiplatform.googleapis.com/v1/projects/config-project/locations/global", + }) + }), + ), + ) + + it.effect("defaults location to us-central1 when only project is configured", () => + withEnv( + { + GOOGLE_CLOUD_PROJECT: undefined, + GCP_PROJECT: undefined, + GCLOUD_PROJECT: undefined, + GOOGLE_VERTEX_LOCATION: undefined, + GOOGLE_CLOUD_LOCATION: undefined, + VERTEX_LOCATION: undefined, + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("google-vertex", { + options: { headers: {}, body: {}, aisdk: { provider: { project: "config-project" }, request: {} } }, + }), + cancel: false, + }, + ) + expect(result.provider.options.aisdk.provider.project).toBe("config-project") + expect(result.provider.options.aisdk.provider.location).toBe("us-central1") + }), + ), + ) + + it.effect("does not pass Google auth fetch to the native Vertex SDK", () => + withEnv( + { + GOOGLE_CLOUD_PROJECT: "env-project", + GOOGLE_VERTEX_LOCATION: "env-location", + }, + () => + Effect.gen(function* () { + vertexOptions.length = 0 + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexPlugin) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("google-vertex", "gemini", { + endpoint: { type: "aisdk", package: "@ai-sdk/google-vertex" }, + }), + package: "@ai-sdk/google-vertex", + options: { name: "google-vertex" }, + }, + {}, + ) + expect(vertexOptions).toHaveLength(1) + expect(vertexOptions[0].project).toBe("env-project") + expect(vertexOptions[0].location).toBe("env-location") + expect(vertexOptions[0].fetch).toBeUndefined() + }), + ), + ) + + it.effect("keeps Google auth fetch for OpenAI-compatible Vertex endpoints", () => + Effect.gen(function* () { + const fetchCalls: { input: Parameters[0]; init?: RequestInit }[] = [] + const plugin = yield* PluginV2.Service + yield* plugin.add(GoogleVertexPlugin) + yield* plugin.add({ + id: PluginV2.ID.make("capture-openai-compatible"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.promise(async () => { + if (evt.model.providerID !== "google-vertex") return + if (evt.package !== "@ai-sdk/openai-compatible") return + expect(typeof evt.options.fetch).toBe("function") + await evt.options.fetch("https://vertex.example", { + headers: { "x-test": "1" }, + }) + }), + }), + }) + const originalFetch = fetch + ;(globalThis as typeof globalThis & { fetch: typeof fetch }).fetch = (async ( + input: Parameters[0], + init?: RequestInit, + ) => { + fetchCalls.push({ input, init }) + return new Response("ok") + }) as typeof fetch + yield* Effect.acquireUseRelease( + Effect.void, + () => + plugin.trigger( + "aisdk.sdk", + { + model: model("google-vertex", "gemini", { + endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible" }, + }), + package: "@ai-sdk/openai-compatible", + options: { name: "google-vertex" }, + }, + {}, + ), + () => + Effect.sync(() => { + ;(globalThis as typeof globalThis & { fetch: typeof fetch }).fetch = originalFetch + }), + ) + expect(fetchCalls).toHaveLength(1) + expect(fetchCalls[0].input).toBe("https://vertex.example") + expect(new Headers(fetchCalls[0].init?.headers).get("authorization")).toBe("Bearer vertex-token") + expect(new Headers(fetchCalls[0].init?.headers).get("x-test")).toBe("1") + }), + ) + + it.effect("trims model IDs before selecting language models", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(GoogleVertexPlugin) + yield* plugin.trigger( + "aisdk.language", + { + model: model("google-vertex", " gemini-2.5-pro "), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: {}, + }, + {}, + ) + expect(calls).toEqual(["languageModel:gemini-2.5-pro"]) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-google.test.ts b/packages/core/test/v2/plugin/provider-google.test.ts new file mode 100644 index 000000000..ee33b980b --- /dev/null +++ b/packages/core/test/v2/plugin/provider-google.test.ts @@ -0,0 +1,70 @@ +import { describe, expect } from "bun:test" +import { Effect, Layer } from "effect" +import { AISDK } from "@opencode-ai/core/aisdk" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { GooglePlugin } from "@opencode-ai/core/plugin/provider/google" +import { testEffect } from "../../lib/effect" +import { it, model } from "./provider-helper" + +const itWithAISDK = testEffect(AISDK.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer))) + +describe("GooglePlugin", () => { + it.effect("creates a Google Generative AI SDK for @ai-sdk/google using the provider ID as SDK name", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GooglePlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-google", "gemini"), + package: "@ai-sdk/google", + options: { name: "custom-google", apiKey: "test" }, + }, + {}, + ) + expect(result.sdk).toBeDefined() + expect(result.sdk?.languageModel("gemini").provider).toBe("custom-google") + }), + ) + + it.effect("ignores non-Google SDK packages", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GooglePlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("google", "gemini"), package: "@ai-sdk/google-vertex", options: { name: "google" } }, + {}, + ) + expect(result.sdk).toBeUndefined() + }), + ) + + itWithAISDK.effect("uses default languageModel loading with provider ID parity", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const aisdk = yield* AISDK.Service + yield* plugin.add(GooglePlugin) + const language = yield* aisdk.language( + model("custom-google", "alias", { + apiID: ModelV2.ID.make("gemini-api"), + endpoint: { + type: "aisdk", + package: "@ai-sdk/google", + }, + options: { + headers: {}, + body: {}, + aisdk: { + provider: { apiKey: "test" }, + request: {}, + }, + }, + }), + ) + expect(language.modelId).toBe("gemini-api") + expect(language.provider).toBe("custom-google") + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-groq.test.ts b/packages/core/test/v2/plugin/provider-groq.test.ts new file mode 100644 index 000000000..14c10b651 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-groq.test.ts @@ -0,0 +1,101 @@ +import { describe, expect } from "bun:test" +import { createGroq } from "@ai-sdk/groq" +import { Effect, Layer } from "effect" +import { AISDK } from "@opencode-ai/core/aisdk" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { GroqPlugin } from "@opencode-ai/core/plugin/provider/groq" +import { it, model } from "./provider-helper" +import { testEffect } from "../../lib/effect" + +const aisdkIt = testEffect(AISDK.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer))) + +describe("GroqPlugin", () => { + it.effect("creates a Groq SDK for @ai-sdk/groq", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GroqPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("groq", "llama"), package: "@ai-sdk/groq", options: { name: "groq" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("ignores non-Groq SDK packages", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GroqPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("groq", "llama"), package: "@ai-sdk/openai-compatible", options: { name: "groq" } }, + {}, + ) + expect(result.sdk).toBeUndefined() + }), + ) + + it.effect("only matches the bundled @ai-sdk/groq package exactly", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GroqPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("groq", "llama"), package: "@ai-sdk/groq/compat", options: { name: "groq" } }, + {}, + ) + expect(result.sdk).toBeUndefined() + }), + ) + + it.effect("matches the old bundled Groq SDK provider naming", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(GroqPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-groq", "llama"), + package: "@ai-sdk/groq", + options: { name: "custom-groq", apiKey: "test" }, + }, + {}, + ) + const expected = createGroq({ name: "custom-groq", apiKey: "test" } as Parameters[0] & { + name: string + }).languageModel("llama") + const actual = result.sdk?.languageModel("llama") + expect(actual?.provider).toBe(expected.provider) + expect(actual?.modelId).toBe(expected.modelId) + }), + ) + + aisdkIt.effect("uses the default languageModel(apiID) behavior", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const aisdk = yield* AISDK.Service + yield* plugin.add(GroqPlugin) + const result = yield* aisdk.language( + model("groq", "alias", { + apiID: ModelV2.ID.make("llama-api"), + endpoint: { + type: "aisdk", + package: "@ai-sdk/groq", + }, + options: { + headers: {}, + body: {}, + aisdk: { + provider: { apiKey: "test" }, + request: {}, + }, + }, + }), + ) + expect(result.modelId).toBe("llama-api") + expect(result.provider).toBe("groq.chat") + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-helper.ts b/packages/core/test/v2/plugin/provider-helper.ts new file mode 100644 index 000000000..84a3044bf --- /dev/null +++ b/packages/core/test/v2/plugin/provider-helper.ts @@ -0,0 +1,100 @@ +import { Npm } from "@opencode-ai/core/npm" +import type { LanguageModelV3 } from "@ai-sdk/provider" +import { expect } from "bun:test" +import { Effect, Layer, Option } from "effect" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { testEffect } from "../../lib/effect" + +export const fixtureProvider = new URL("./fixtures/provider-factory.ts", import.meta.url).href + +export const npmLayer = Layer.succeed( + Npm.Service, + Npm.Service.of({ + add: () => Effect.succeed({ directory: "", entrypoint: Option.none() }), + install: () => Effect.void, + which: () => Effect.succeed(Option.none()), + }), +) + +export const it = testEffect(Layer.mergeAll(PluginV2.defaultLayer, npmLayer)) + +export function provider(providerID: string, options?: Partial) { + return new ProviderV2.Info({ + ...ProviderV2.Info.empty(ProviderV2.ID.make(providerID)), + endpoint: { + type: "aisdk", + package: "test-provider", + }, + ...options, + options: { + headers: {}, + body: {}, + aisdk: { + provider: {}, + request: {}, + }, + ...options?.options, + }, + }) +} + +export function model(providerID: string, modelID: string, options?: Partial) { + return new ModelV2.Info({ + ...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)), + apiID: ModelV2.ID.make(modelID), + endpoint: { + type: "aisdk", + package: "test-provider", + }, + ...options, + options: { + headers: {}, + body: {}, + aisdk: { + provider: {}, + request: {}, + }, + ...options?.options, + }, + }) +} + +export function withEnv(vars: Record, fx: () => Effect.Effect) { + return Effect.acquireUseRelease( + Effect.sync(() => { + const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]])) + for (const [key, value] of Object.entries(vars)) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + return previous + }), + () => fx(), + (previous) => + Effect.sync(() => { + for (const [key, value] of Object.entries(previous)) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + }), + ) +} + +export function fakeSelectorSdk(calls: string[]) { + const make = (method: string) => (id: string) => { + calls.push(`${method}:${id}`) + return { modelId: id, provider: method, specificationVersion: "v3" } as unknown as LanguageModelV3 + } + return { + responses: make("responses"), + messages: make("messages"), + chat: make("chat"), + languageModel: make("languageModel"), + } +} + +export function expectPluginRegistered(ids: string[], id: string) { + expect(ids).toContain(PluginV2.ID.make(id)) +} diff --git a/packages/core/test/v2/plugin/provider-kilo.test.ts b/packages/core/test/v2/plugin/provider-kilo.test.ts new file mode 100644 index 000000000..4261ae132 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-kilo.test.ts @@ -0,0 +1,90 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { ProviderPlugins } from "@opencode-ai/core/plugin/provider" +import { KiloPlugin } from "@opencode-ai/core/plugin/provider/kilo" +import { expectPluginRegistered, it, provider } from "./provider-helper" + +describe("KiloPlugin", () => { + it.effect("is registered so legacy referer headers can be applied", () => + Effect.sync(() => + expectPluginRegistered( + ProviderPlugins.map((item) => item.id), + "kilo", + ), + ), + ) + + it.effect("applies legacy referer headers only to kilo", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(KiloPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("kilo", { + options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + cancel: false, + }, + ) + const ignored = yield* plugin.trigger("provider.update", {}, { provider: provider("openrouter"), cancel: false }) + expect(result.provider.options.headers).toEqual({ + Existing: "value", + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }) + expect(ignored.provider.options.headers).toEqual({}) + }), + ) + + it.effect("uses the exact legacy Kilo header casing and set", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(KiloPlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("kilo"), cancel: false }) + + expect(result.provider.options.headers).toEqual({ + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }) + expect(result.provider.options.headers).not.toHaveProperty("http-referer") + expect(result.provider.options.headers).not.toHaveProperty("x-title") + expect(result.provider.options.headers).not.toHaveProperty("X-Source") + }), + ) + + it.effect("uses the legacy provider-id guard instead of endpoint package matching", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(KiloPlugin) + const matchingID = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("kilo", { + endpoint: { type: "aisdk", package: "not-kilo" }, + }), + cancel: false, + }, + ) + const matchingPackage = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("custom-kilo", { + endpoint: { type: "aisdk", package: "kilo" }, + }), + cancel: false, + }, + ) + + expect(matchingID.provider.options.headers).toEqual({ + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }) + expect(matchingPackage.provider.options.headers).toEqual({}) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-llmgateway.test.ts b/packages/core/test/v2/plugin/provider-llmgateway.test.ts new file mode 100644 index 000000000..1ffea96bc --- /dev/null +++ b/packages/core/test/v2/plugin/provider-llmgateway.test.ts @@ -0,0 +1,63 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { ProviderPlugins } from "@opencode-ai/core/plugin/provider" +import { LLMGatewayPlugin } from "@opencode-ai/core/plugin/provider/llmgateway" +import { expectPluginRegistered, it, provider } from "./provider-helper" + +describe("LLMGatewayPlugin", () => { + it.effect("is registered so legacy referer headers can be applied", () => + Effect.sync(() => + expectPluginRegistered( + ProviderPlugins.map((item) => item.id), + "llmgateway", + ), + ), + ) + + it.effect("applies legacy referer headers only to enabled llmgateway", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(LLMGatewayPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("llmgateway", { + enabled: { via: "env", name: "LLMGATEWAY_API_KEY" }, + options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + cancel: false, + }, + ) + const ignored = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("openrouter", { + enabled: { via: "env", name: "OPENROUTER_API_KEY" }, + }), + cancel: false, + }, + ) + expect(result.provider.options.headers).toEqual({ + Existing: "value", + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + "X-Source": "opencode", + }) + expect(ignored.provider.options.headers).toEqual({}) + }), + ) + + it.effect("does not apply legacy headers to a disabled llmgateway provider", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(LLMGatewayPlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("llmgateway"), cancel: false }) + + expect(result.provider.enabled).toBe(false) + expect(result.provider.options.headers).toEqual({}) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-mistral.test.ts b/packages/core/test/v2/plugin/provider-mistral.test.ts new file mode 100644 index 000000000..f24ff53e5 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-mistral.test.ts @@ -0,0 +1,106 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { MistralPlugin } from "@opencode-ai/core/plugin/provider/mistral" +import { fakeSelectorSdk, it, model } from "./provider-helper" + +describe("MistralPlugin", () => { + it.effect("creates a Mistral SDK for @ai-sdk/mistral", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(MistralPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("mistral", "mistral-large"), package: "@ai-sdk/mistral", options: { name: "mistral" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("ignores non-Mistral SDK packages", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(MistralPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("mistral", "mistral-large"), + package: "@ai-sdk/openai-compatible", + options: { name: "mistral" }, + }, + {}, + ) + expect(result.sdk).toBeUndefined() + }), + ) + + it.effect("matches the old bundled Mistral SDK provider name for the bundled provider ID", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const providers: string[] = [] + yield* plugin.add(MistralPlugin) + yield* plugin.add({ + id: PluginV2.ID.make("mistral-sdk-inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + providers.push(evt.sdk.languageModel("mistral-large").provider) + }), + }), + }) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("mistral", "mistral-large"), package: "@ai-sdk/mistral", options: { name: "mistral" } }, + {}, + ) + expect(result.sdk).toBeDefined() + expect(providers).toEqual(["mistral.chat"]) + }), + ) + + it.effect("matches the old bundled Mistral SDK provider name for custom provider IDs", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const providers: string[] = [] + yield* plugin.add(MistralPlugin) + yield* plugin.add({ + id: PluginV2.ID.make("mistral-sdk-inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + providers.push(evt.sdk.languageModel("mistral-large").provider) + }), + }), + }) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-mistral", "mistral-large"), + package: "@ai-sdk/mistral", + options: { name: "custom-mistral" }, + }, + {}, + ) + expect(providers).toEqual(["mistral.chat"]) + }), + ) + + it.effect("leaves Mistral language selection on the default sdk.languageModel(apiID) path", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + const sdk = fakeSelectorSdk(calls) + yield* plugin.add(MistralPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { model: model("mistral", "alias", { apiID: ModelV2.ID.make("mistral-large") }), sdk, options: {} }, + {}, + ) + const language = result.language ?? sdk.languageModel(result.model.apiID) + expect(calls).toEqual(["languageModel:mistral-large"]) + expect(language).toBeDefined() + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-nvidia.test.ts b/packages/core/test/v2/plugin/provider-nvidia.test.ts new file mode 100644 index 000000000..0e06356cd --- /dev/null +++ b/packages/core/test/v2/plugin/provider-nvidia.test.ts @@ -0,0 +1,41 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { ProviderPlugins } from "@opencode-ai/core/plugin/provider" +import { NvidiaPlugin } from "@opencode-ai/core/plugin/provider/nvidia" +import { expectPluginRegistered, it, provider } from "./provider-helper" + +describe("NvidiaPlugin", () => { + it.effect("is registered so legacy referer headers can be applied", () => + Effect.sync(() => + expectPluginRegistered( + ProviderPlugins.map((item) => item.id), + "nvidia", + ), + ), + ) + + it.effect("applies legacy referer headers only to nvidia", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(NvidiaPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("nvidia", { + options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + cancel: false, + }, + ) + const ignored = yield* plugin.trigger("provider.update", {}, { provider: provider("openrouter"), cancel: false }) + expect(result.provider.options.headers).toEqual({ + Existing: "value", + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }) + expect(ignored.provider.options.headers).toEqual({}) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-openai-compatible.test.ts b/packages/core/test/v2/plugin/provider-openai-compatible.test.ts new file mode 100644 index 000000000..e8bf1f757 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-openai-compatible.test.ts @@ -0,0 +1,101 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { OpenAICompatiblePlugin } from "@opencode-ai/core/plugin/provider/openai-compatible" +import { it, model } from "./provider-helper" + +describe("OpenAICompatiblePlugin", () => { + it.effect("preserves explicit includeUsage false and defaults it to true", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenAICompatiblePlugin) + const defaulted = yield* plugin.trigger( + "aisdk.sdk", + { model: model("custom", "model"), package: "@ai-sdk/openai-compatible", options: { name: "custom" } }, + {}, + ) + const disabled = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom", "model"), + package: "@ai-sdk/openai-compatible", + options: { name: "custom", includeUsage: false }, + }, + {}, + ) + expect(defaulted.options.includeUsage).toBe(true) + expect(disabled.options.includeUsage).toBe(false) + }), + ) + + it.effect("defaults includeUsage for OpenAI-compatible package matches", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenAICompatiblePlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom", "model"), + package: "file:///tmp/@ai-sdk/openai-compatible-provider.js", + options: { name: "custom" }, + }, + {}, + ) + expect(result.options.includeUsage).toBe(true) + }), + ) + + it.effect("uses the provider ID as the OpenAI-compatible provider name", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const observed: string[] = [] + yield* plugin.add(OpenAICompatiblePlugin) + yield* plugin.add({ + id: PluginV2.ID.make("inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + observed.push(evt.sdk.languageModel("model").provider) + }), + }), + }) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-provider", "model"), + package: "@ai-sdk/openai-compatible", + options: { name: "custom-provider", baseURL: "https://example.com/v1" }, + }, + {}, + ) + expect(observed).toEqual(["custom-provider.chat"]) + }), + ) + + it.effect("does not overwrite an SDK created by an earlier provider-specific plugin", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const sentinel = { languageModel: (modelID: string) => ({ modelID }) } + yield* plugin.add({ + id: PluginV2.ID.make("sentinel"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + evt.sdk = sentinel + }), + }), + }) + yield* plugin.add(OpenAICompatiblePlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("cloudflare-workers-ai", "model"), + package: "@ai-sdk/openai-compatible", + options: { name: "cloudflare-workers-ai" }, + }, + {}, + ) + expect(result.sdk).toBe(sentinel) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-openai.test.ts b/packages/core/test/v2/plugin/provider-openai.test.ts new file mode 100644 index 000000000..31d6dd0b6 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-openai.test.ts @@ -0,0 +1,100 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { OpenAIPlugin } from "@opencode-ai/core/plugin/provider/openai" +import { fakeSelectorSdk, it, model } from "./provider-helper" + +describe("OpenAIPlugin", () => { + it.effect("creates an OpenAI SDK for @ai-sdk/openai using the provider ID as SDK name", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenAIPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-openai", "gpt-5"), + package: "@ai-sdk/openai", + options: { name: "custom-openai", apiKey: "test" }, + }, + {}, + ) + expect(result.sdk?.responses("gpt-5").provider).toBe("custom-openai.responses") + }), + ) + + it.effect("ignores non-OpenAI SDK packages", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenAIPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("openai", "gpt-5"), package: "@ai-sdk/openai-compatible", options: { name: "openai" } }, + {}, + ) + expect(result.sdk).toBeUndefined() + }), + ) + + it.effect("uses the Responses API for language models", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(OpenAIPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: model("openai", "alias", { apiID: ModelV2.ID.make("gpt-5") }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + expect(calls).toEqual(["responses:gpt-5"]) + expect(result.language).toBeDefined() + }), + ) + + it.effect("ignores non-OpenAI providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(OpenAIPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { model: model("anthropic", "gpt-5"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + expect(calls).toEqual([]) + expect(result.language).toBeUndefined() + }), + ) + + it.effect("cancels gpt-5-chat-latest during model updates", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenAIPlugin) + const normal = yield* plugin.trigger("model.update", {}, { model: model("openai", "gpt-5"), cancel: false }) + const filtered = yield* plugin.trigger( + "model.update", + {}, + { model: model("openai", "gpt-5-chat-latest"), cancel: false }, + ) + expect(normal.cancel).toBe(false) + expect(filtered.cancel).toBe(true) + }), + ) + + it.effect("does not cancel gpt-5-chat-latest for non-OpenAI providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenAIPlugin) + const result = yield* plugin.trigger( + "model.update", + {}, + { model: model("custom-openai", "gpt-5-chat-latest"), cancel: false }, + ) + expect(result.cancel).toBe(false) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-opencode.test.ts b/packages/core/test/v2/plugin/provider-opencode.test.ts new file mode 100644 index 000000000..f080776d4 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-opencode.test.ts @@ -0,0 +1,195 @@ +import { describe, expect } from "bun:test" +import { DateTime, Effect, Option } from "effect" +import { Catalog } from "@opencode-ai/core/catalog" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { it, model, provider, withEnv } from "./provider-helper" + +const cost = (input: number, output = 0) => [{ input, output, cache: { read: 0, write: 0 } }] + +describe("OpencodePlugin", () => { + it.effect("uses a public key and cancels paid models without credentials", () => + withEnv({ OPENCODE_API_KEY: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpencodePlugin) + const updated = yield* plugin.trigger("provider.update", {}, { provider: provider("opencode"), cancel: false }) + const paid = yield* plugin.trigger( + "model.update", + {}, + { model: model("opencode", "paid", { cost: cost(1) }), cancel: false }, + ) + expect(updated.provider.options.aisdk.provider.apiKey).toBe("public") + expect(paid.cancel).toBe(true) + }), + ), + ) + + it.effect("keeps free models without credentials", () => + withEnv({ OPENCODE_API_KEY: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpencodePlugin) + yield* plugin.trigger("provider.update", {}, { provider: provider("opencode"), cancel: false }) + const free = yield* plugin.trigger( + "model.update", + {}, + { model: model("opencode", "free", { cost: cost(0) }), cancel: false }, + ) + expect(free.cancel).toBe(false) + }), + ), + ) + + it.effect("treats output-only cost as free without credentials", () => + withEnv({ OPENCODE_API_KEY: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpencodePlugin) + yield* plugin.trigger("provider.update", {}, { provider: provider("opencode"), cancel: false }) + const outputOnly = yield* plugin.trigger( + "model.update", + {}, + { model: model("opencode", "output-only", { cost: cost(0, 1) }), cancel: false }, + ) + expect(outputOnly.cancel).toBe(false) + }), + ), + ) + + it.effect("uses OPENCODE_API_KEY as credentials", () => + withEnv({ OPENCODE_API_KEY: "secret" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpencodePlugin) + const updated = yield* plugin.trigger("provider.update", {}, { provider: provider("opencode"), cancel: false }) + const paid = yield* plugin.trigger( + "model.update", + {}, + { model: model("opencode", "paid", { cost: cost(1) }), cancel: false }, + ) + expect(updated.provider.options.aisdk.provider.apiKey).toBeUndefined() + expect(paid.cancel).toBe(false) + }), + ), + ) + + it.effect("uses configured provider env vars as credentials", () => + withEnv({ OPENCODE_API_KEY: undefined, CUSTOM_OPENCODE_API_KEY: "secret" }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpencodePlugin) + const updated = yield* plugin.trigger( + "provider.update", + {}, + { provider: provider("opencode", { env: ["CUSTOM_OPENCODE_API_KEY"] }), cancel: false }, + ) + const paid = yield* plugin.trigger( + "model.update", + {}, + { model: model("opencode", "paid", { cost: cost(1) }), cancel: false }, + ) + expect(updated.provider.options.aisdk.provider.apiKey).toBeUndefined() + expect(paid.cancel).toBe(false) + }), + ), + ) + + it.effect("uses configured apiKey as credentials", () => + withEnv({ OPENCODE_API_KEY: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpencodePlugin) + const updated = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("opencode", { + options: { + headers: {}, + body: {}, + aisdk: { + provider: { apiKey: "configured" }, + request: {}, + }, + }, + }), + cancel: false, + }, + ) + const paid = yield* plugin.trigger( + "model.update", + {}, + { model: model("opencode", "paid", { cost: cost(1) }), cancel: false }, + ) + expect(updated.provider.options.aisdk.provider.apiKey).toBe("configured") + expect(paid.cancel).toBe(false) + }), + ), + ) + + it.effect("uses auth-enabled providers as credentials", () => + withEnv({ OPENCODE_API_KEY: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpencodePlugin) + const updated = yield* plugin.trigger( + "provider.update", + {}, + { provider: provider("opencode", { enabled: { via: "auth", service: "opencode" } }), cancel: false }, + ) + const paid = yield* plugin.trigger( + "model.update", + {}, + { model: model("opencode", "paid", { cost: cost(1) }), cancel: false }, + ) + expect(updated.provider.options.aisdk.provider.apiKey).toBeUndefined() + expect(paid.cancel).toBe(false) + }), + ), + ) + + it.effect("ignores non-opencode providers and models", () => + withEnv({ OPENCODE_API_KEY: undefined }, () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpencodePlugin) + const updated = yield* plugin.trigger("provider.update", {}, { provider: provider("openai"), cancel: false }) + const paid = yield* plugin.trigger( + "model.update", + {}, + { model: model("openai", "paid", { cost: cost(1) }), cancel: false }, + ) + expect(updated.provider.options.aisdk.provider.apiKey).toBeUndefined() + expect(paid.cancel).toBe(false) + }), + ), + ) + + it.effect("prefers gpt-5-nano as the opencode small model", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.opencode + + yield* catalog.provider.update(providerID, () => {}) + yield* catalog.model.update(providerID, ModelV2.ID.make("cheap-mini"), (model) => { + model.capabilities.input = ["text"] + model.capabilities.output = ["text"] + model.cost = cost(1, 1) + model.time.released = DateTime.makeUnsafe(Date.now()) + }) + yield* catalog.model.update(providerID, ModelV2.ID.make("gpt-5-nano"), (model) => { + model.capabilities.input = ["text"] + model.capabilities.output = ["text"] + model.cost = cost(10, 10) + model.time.released = DateTime.makeUnsafe(Date.now()) + }) + + const selected = yield* catalog.model.small(providerID) + + expect(Option.getOrUndefined(selected)?.id).toBe(ModelV2.ID.make("gpt-5-nano")) + }).pipe(Effect.provide(Catalog.defaultLayer)), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-openrouter.test.ts b/packages/core/test/v2/plugin/provider-openrouter.test.ts new file mode 100644 index 000000000..3d143ac7f --- /dev/null +++ b/packages/core/test/v2/plugin/provider-openrouter.test.ts @@ -0,0 +1,105 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { ProviderPlugins } from "@opencode-ai/core/plugin/provider" +import { OpenRouterPlugin } from "@opencode-ai/core/plugin/provider/openrouter" +import { expectPluginRegistered, it, model, provider } from "./provider-helper" + +describe("OpenRouterPlugin", () => { + it.effect("is registered so legacy OpenRouter behavior can be applied", () => + Effect.sync(() => + expectPluginRegistered( + ProviderPlugins.map((item) => item.id), + "openrouter", + ), + ), + ) + + it.effect("applies legacy referer headers only to openrouter", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenRouterPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("openrouter", { + options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + cancel: false, + }, + ) + const ignored = yield* plugin.trigger("provider.update", {}, { provider: provider("nvidia"), cancel: false }) + expect(result.provider.options.headers).toEqual({ + Existing: "value", + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }) + expect(ignored.provider.options.headers).toEqual({}) + }), + ) + + it.effect("creates an SDK only for the OpenRouter package", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenRouterPlugin) + + const ignored = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("openrouter", "openai/gpt-5"), + package: "@ai-sdk/openai-compatible", + options: { name: "openrouter" }, + }, + {}, + ) + expect(ignored.sdk).toBeUndefined() + + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("custom", "openai/gpt-5"), package: "@openrouter/ai-sdk-provider", options: { name: "custom" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("filters OpenRouter's gpt-5 chat alias", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenRouterPlugin) + const result = yield* plugin.trigger( + "model.update", + {}, + { model: model("openrouter", "openai/gpt-5-chat"), cancel: false }, + ) + const regular = yield* plugin.trigger( + "model.update", + {}, + { model: model("openrouter", "openai/gpt-5"), cancel: false }, + ) + const ignored = yield* plugin.trigger( + "model.update", + {}, + { model: model("openai", "openai/gpt-5-chat"), cancel: false }, + ) + + expect(result.cancel).toBe(true) + expect(regular.cancel).toBe(false) + expect(ignored.cancel).toBe(false) + }), + ) + + it.effect("does not filter gpt-5-chat-latest for non-OpenRouter providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(OpenRouterPlugin) + const result = yield* plugin.trigger( + "model.update", + {}, + { model: model("custom-openrouter", "gpt-5-chat-latest"), cancel: false }, + ) + expect(result.cancel).toBe(false) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-perplexity.test.ts b/packages/core/test/v2/plugin/provider-perplexity.test.ts new file mode 100644 index 000000000..d03f58337 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-perplexity.test.ts @@ -0,0 +1,107 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { PerplexityPlugin } from "@opencode-ai/core/plugin/provider/perplexity" +import { fakeSelectorSdk, it, model } from "./provider-helper" + +describe("PerplexityPlugin", () => { + it.effect("creates a Perplexity SDK for the exact @ai-sdk/perplexity package", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(PerplexityPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("perplexity", "sonar"), package: "@ai-sdk/perplexity", options: { name: "perplexity" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("ignores packages that are not the bundled Perplexity package", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(PerplexityPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("perplexity", "sonar"), + package: "@ai-sdk/perplexity-compatible", + options: { name: "perplexity" }, + }, + {}, + ) + expect(result.sdk).toBeUndefined() + }), + ) + + it.effect("uses the Perplexity provider ID as the SDK name for the bundled provider", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const providers: string[] = [] + yield* plugin.add(PerplexityPlugin) + yield* plugin.add({ + id: PluginV2.ID.make("perplexity-sdk-inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + providers.push(evt.sdk.languageModel("sonar").provider) + }), + }), + }) + yield* plugin.trigger( + "aisdk.sdk", + { model: model("perplexity", "sonar"), package: "@ai-sdk/perplexity", options: { name: "perplexity" } }, + {}, + ) + expect(providers).toEqual(["perplexity"]) + }), + ) + + it.effect("creates bundled Perplexity SDKs for custom provider IDs", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const providers: string[] = [] + yield* plugin.add(PerplexityPlugin) + yield* plugin.add({ + id: PluginV2.ID.make("custom-perplexity-sdk-inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + providers.push(evt.sdk.languageModel("sonar").provider) + }), + }), + }) + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-perplexity", "sonar"), + package: "@ai-sdk/perplexity", + options: { name: "custom-perplexity" }, + }, + {}, + ) + expect(providers).toEqual(["perplexity"]) + }), + ) + + it.effect("leaves Perplexity language selection to the default languageModel fallback", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(PerplexityPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: model("perplexity", "alias", { apiID: ModelV2.ID.make("sonar") }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + expect(calls).toEqual([]) + expect(result.language).toBeUndefined() + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-sap-ai-core.test.ts b/packages/core/test/v2/plugin/provider-sap-ai-core.test.ts new file mode 100644 index 000000000..565b9280a --- /dev/null +++ b/packages/core/test/v2/plugin/provider-sap-ai-core.test.ts @@ -0,0 +1,127 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { SapAICorePlugin } from "@opencode-ai/core/plugin/provider/sap-ai-core" +import { fixtureProvider, it, model, npmLayer, withEnv } from "./provider-helper" + +const pluginWithNpm = { id: SapAICorePlugin.id, effect: SapAICorePlugin.effect.pipe(Effect.provide(npmLayer)) } + +describe("SapAICorePlugin", () => { + it.effect("copies serviceKey option into AICORE_SERVICE_KEY but keeps SDK options to deployment metadata", () => + withEnv( + { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(pluginWithNpm) + const sdk = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("sap-ai-core", "sap-model"), + package: fixtureProvider, + options: { name: "sap-ai-core", serviceKey: "service-key" }, + }, + {}, + ) + expect(process.env.AICORE_SERVICE_KEY).toBe("service-key") + expect(sdk.sdk.options).toEqual({ deploymentId: "deployment", resourceGroup: "resource-group" }) + }), + ), + ) + + it.effect("preserves existing AICORE_SERVICE_KEY over serviceKey option", () => + withEnv( + { + AICORE_SERVICE_KEY: "env-service-key", + AICORE_DEPLOYMENT_ID: "deployment", + AICORE_RESOURCE_GROUP: "resource-group", + }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(pluginWithNpm) + const sdk = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("sap-ai-core", "sap-model"), + package: fixtureProvider, + options: { name: "sap-ai-core", serviceKey: "option-service-key" }, + }, + {}, + ) + expect(process.env.AICORE_SERVICE_KEY).toBe("env-service-key") + expect(sdk.sdk.options).toEqual({ deploymentId: "deployment", resourceGroup: "resource-group" }) + }), + ), + ) + + it.effect("omits deployment and resourceGroup SDK options when no service key is available", () => + withEnv( + { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(pluginWithNpm) + const sdk = yield* plugin.trigger( + "aisdk.sdk", + { model: model("sap-ai-core", "sap-model"), package: fixtureProvider, options: { name: "sap-ai-core" } }, + {}, + ) + expect(process.env.AICORE_SERVICE_KEY).toBeUndefined() + expect(sdk.sdk.options).toEqual({}) + }), + ), + ) + + it.effect("uses the callable SDK for language selection", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(pluginWithNpm) + const sdk = Object.assign((modelID: string) => ({ modelID, provider: "callable" }), { + languageModel() { + throw new Error("SAP AI Core should call the SDK directly") + }, + }) + const language = yield* plugin.trigger( + "aisdk.language", + { model: model("sap-ai-core", "sap-model"), sdk, options: {} }, + {}, + ) + expect(language.language as unknown).toEqual({ modelID: "sap-model", provider: "callable" }) + }), + ) + + it.effect("ignores non-SAP AI Core providers", () => + withEnv( + { AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" }, + () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(pluginWithNpm) + const sdk = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("openai", "sap-model"), + package: fixtureProvider, + options: { name: "openai", serviceKey: "service-key" }, + }, + {}, + ) + const language = yield* plugin.trigger( + "aisdk.language", + { + model: model("openai", "sap-model"), + sdk: () => { + throw new Error("SAP AI Core should ignore other providers") + }, + options: {}, + }, + {}, + ) + expect(process.env.AICORE_SERVICE_KEY).toBeUndefined() + expect(sdk.sdk).toBeUndefined() + expect(language.language).toBeUndefined() + }), + ), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-togetherai.test.ts b/packages/core/test/v2/plugin/provider-togetherai.test.ts new file mode 100644 index 000000000..65090037b --- /dev/null +++ b/packages/core/test/v2/plugin/provider-togetherai.test.ts @@ -0,0 +1,97 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { TogetherAIPlugin } from "@opencode-ai/core/plugin/provider/togetherai" +import { fakeSelectorSdk, it, model } from "./provider-helper" + +describe("TogetherAIPlugin", () => { + it.effect("creates a TogetherAI SDK for @ai-sdk/togetherai", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(TogetherAIPlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("togetherai", "model"), package: "@ai-sdk/togetherai", options: { name: "togetherai" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("matches the old bundled provider package exactly", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(TogetherAIPlugin) + + const ignored = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("togetherai", "model"), + package: "file:///tmp/@ai-sdk/togetherai-provider.js", + options: { name: "togetherai" }, + }, + {}, + ) + expect(ignored.sdk).toBeUndefined() + + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("togetherai", "model"), package: "@ai-sdk/togetherai", options: { name: "togetherai" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("creates bundled TogetherAI SDKs for custom provider IDs", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const observed: string[] = [] + yield* plugin.add(TogetherAIPlugin) + yield* plugin.add({ + id: PluginV2.ID.make("inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + observed.push(evt.sdk.languageModel("model").provider) + }), + }), + }) + + yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-togetherai", "model"), + package: "@ai-sdk/togetherai", + options: { name: "custom-togetherai" }, + }, + {}, + ) + + expect(observed).toEqual(["togetherai.chat"]) + }), + ) + + it.effect("defaults language selection to sdk.languageModel with the model API ID", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(TogetherAIPlugin) + + const result = yield* plugin.trigger( + "aisdk.language", + { + model: model("togetherai", "meta-llama/Llama-3.3-70B-Instruct-Turbo"), + sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, + options: {}, + }, + {}, + ) + + expect(result.language).toBeUndefined() + expect(calls).toEqual([]) + expect(result.language ?? fakeSelectorSdk(calls).languageModel(result.model.apiID)).toBeDefined() + expect(calls).toEqual(["languageModel:meta-llama/Llama-3.3-70B-Instruct-Turbo"]) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-venice.test.ts b/packages/core/test/v2/plugin/provider-venice.test.ts new file mode 100644 index 000000000..ff4a922ab --- /dev/null +++ b/packages/core/test/v2/plugin/provider-venice.test.ts @@ -0,0 +1,86 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { VenicePlugin } from "@opencode-ai/core/plugin/provider/venice" +import { fakeSelectorSdk, it, model } from "./provider-helper" + +describe("VenicePlugin", () => { + it.effect("creates a Venice SDK for venice-ai-sdk-provider", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(VenicePlugin) + const result = yield* plugin.trigger( + "aisdk.sdk", + { model: model("venice", "model"), package: "venice-ai-sdk-provider", options: { name: "venice" } }, + {}, + ) + expect(result.sdk).toBeDefined() + }), + ) + + it.effect("uses the model provider ID as the bundled Venice SDK name", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const observed: string[] = [] + yield* plugin.add(VenicePlugin) + yield* plugin.add({ + id: PluginV2.ID.make("inspector"), + effect: Effect.succeed({ + "aisdk.sdk": (evt) => + Effect.sync(() => { + observed.push(evt.sdk.languageModel("model").provider) + }), + }), + }) + const result = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("custom-venice", "model"), + package: "venice-ai-sdk-provider", + options: { name: "custom-venice", apiKey: "test" }, + }, + {}, + ) + expect(result.sdk).toBeDefined() + expect(observed).toEqual(["custom-venice.chat"]) + }), + ) + + it.effect("only handles the bundled venice-ai-sdk-provider package", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(VenicePlugin) + const similar = yield* plugin.trigger( + "aisdk.sdk", + { + model: model("venice", "model"), + package: "file:///tmp/venice-ai-sdk-provider.js", + options: { name: "venice" }, + }, + {}, + ) + const other = yield* plugin.trigger( + "aisdk.sdk", + { model: model("venice", "model"), package: "@ai-sdk/openai-compatible", options: { name: "venice" } }, + {}, + ) + expect(similar.sdk).toBeUndefined() + expect(other.sdk).toBeUndefined() + }), + ) + + it.effect("leaves Venice language selection to the default languageModel fallback", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + yield* plugin.add(VenicePlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { model: model("venice", "alias"), sdk: fakeSelectorSdk(calls), options: {} }, + {}, + ) + expect(calls).toEqual([]) + expect(result.language).toBeUndefined() + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-vercel.test.ts b/packages/core/test/v2/plugin/provider-vercel.test.ts new file mode 100644 index 000000000..3134a7b83 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-vercel.test.ts @@ -0,0 +1,62 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { VercelPlugin } from "@opencode-ai/core/plugin/provider/vercel" +import { it, model, provider } from "./provider-helper" + +describe("VercelPlugin", () => { + it.effect("applies legacy lower-case referer headers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(VercelPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("vercel", { + options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + cancel: false, + }, + ) + expect(result.provider.options.headers).toEqual({ + Existing: "1", + "http-referer": "https://opencode.ai/", + "x-title": "opencode", + }) + }), + ) + + it.effect("does not add legacy upper-case referer headers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(VercelPlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("vercel"), cancel: false }) + expect(result.provider.options.headers).not.toHaveProperty("HTTP-Referer") + expect(result.provider.options.headers).not.toHaveProperty("X-Title") + }), + ) + + it.effect("creates @ai-sdk/vercel SDKs for custom provider IDs", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(VercelPlugin) + const event = yield* plugin.trigger( + "aisdk.sdk", + { model: model("custom-vercel", "v0-1.0-md"), package: "@ai-sdk/vercel", options: { name: "custom-vercel" } }, + {}, + ) + expect(event.sdk).toBeDefined() + expect(event.sdk.languageModel("v0-1.0-md").provider).toBe("vercel.chat") + }), + ) + + it.effect("ignores non-Vercel providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(VercelPlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("gateway"), cancel: false }) + expect(result.provider.options.headers).toEqual({}) + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-xai.test.ts b/packages/core/test/v2/plugin/provider-xai.test.ts new file mode 100644 index 000000000..bb2828ff4 --- /dev/null +++ b/packages/core/test/v2/plugin/provider-xai.test.ts @@ -0,0 +1,115 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { ModelV2 } from "@opencode-ai/core/model" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { XAIPlugin } from "@opencode-ai/core/plugin/provider/xai" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { testEffect } from "../../lib/effect" +import { fakeSelectorSdk } from "./provider-helper" + +const it = testEffect(PluginV2.defaultLayer) + +const model = new ModelV2.Info({ + ...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")), + apiID: ModelV2.ID.make("grok-4"), + endpoint: { + type: "aisdk", + package: "@ai-sdk/xai", + }, +}) + +describe("XAIPlugin", () => { + it.effect("creates an xAI SDK only for @ai-sdk/xai", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(XAIPlugin) + + const ignored = yield* plugin.trigger( + "aisdk.sdk", + { model, package: "@ai-sdk/openai-compatible", options: {} }, + {}, + ) + + const result = yield* plugin.trigger("aisdk.sdk", { model, package: "@ai-sdk/xai", options: {} }, {}) + + expect(ignored.sdk).toBeUndefined() + expect(typeof result.sdk?.responses).toBe("function") + }), + ) + + it.effect("creates xAI SDKs for custom provider IDs", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const providers: string[] = [] + + yield* plugin.add(XAIPlugin) + yield* plugin.add( + PluginV2.define({ + id: PluginV2.ID.make("xai-sdk-name-observer"), + effect: Effect.gen(function* () { + return { + "aisdk.sdk": Effect.fn(function* (evt) { + if (!evt.sdk) return + providers.push(evt.sdk.responses("grok-4").provider) + }), + } + }), + }), + ) + + yield* plugin.trigger( + "aisdk.sdk", + { + model: new ModelV2.Info({ ...model, providerID: ProviderV2.ID.make("custom-xai") }), + package: "@ai-sdk/xai", + options: {}, + }, + {}, + ) + + expect(providers).toEqual(["xai.responses"]) + }), + ) + + it.effect("uses responses with the model apiID for xAI language models", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + + yield* plugin.add(XAIPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: new ModelV2.Info({ ...model, id: ModelV2.ID.make("alias"), apiID: ModelV2.ID.make("grok-4") }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + + expect(calls).toEqual(["responses:grok-4"]) + expect(result.language).toBeDefined() + }), + ) + + it.effect("ignores non-xAI providers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + const calls: string[] = [] + + yield* plugin.add(XAIPlugin) + const result = yield* plugin.trigger( + "aisdk.language", + { + model: new ModelV2.Info({ ...model, providerID: ProviderV2.ID.openai }), + sdk: fakeSelectorSdk(calls), + options: {}, + }, + {}, + ) + + expect(calls).toEqual([]) + expect(result.language).toBeUndefined() + }), + ) +}) diff --git a/packages/core/test/v2/plugin/provider-zenmux.test.ts b/packages/core/test/v2/plugin/provider-zenmux.test.ts new file mode 100644 index 000000000..2b7730e6c --- /dev/null +++ b/packages/core/test/v2/plugin/provider-zenmux.test.ts @@ -0,0 +1,103 @@ +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { ProviderPlugins } from "@opencode-ai/core/plugin/provider" +import { ZenmuxPlugin } from "@opencode-ai/core/plugin/provider/zenmux" +import { expectPluginRegistered, it, provider } from "./provider-helper" + +describe("ZenmuxPlugin", () => { + it.effect("is registered so legacy referer headers can be applied", () => + Effect.sync(() => + expectPluginRegistered( + ProviderPlugins.map((item) => item.id), + "zenmux", + ), + ), + ) + + it.effect("applies the exact legacy Zenmux headers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(ZenmuxPlugin) + const result = yield* plugin.trigger("provider.update", {}, { provider: provider("zenmux"), cancel: false }) + expect(result.provider.options.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" }) + expect(Object.keys(result.provider.options.headers).sort()).toEqual(["HTTP-Referer", "X-Title"]) + expect(result.cancel).toBe(false) + }), + ) + + it.effect("merges legacy Zenmux headers with existing headers", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(ZenmuxPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("zenmux", { + options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + }), + cancel: false, + }, + ) + + expect(result.provider.options.headers).toEqual({ + Existing: "value", + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }) + }), + ) + + it.effect("lets configured Zenmux legacy headers override defaults", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(ZenmuxPlugin) + const result = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("zenmux", { + options: { + headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" }, + body: {}, + aisdk: { provider: {}, request: {} }, + }, + }), + cancel: false, + }, + ) + + expect(result.provider.options.headers).toEqual({ + "HTTP-Referer": "https://example.com/", + "X-Title": "custom-title", + }) + }), + ) + + it.effect("guards legacy Zenmux headers to the exact zenmux provider id", () => + Effect.gen(function* () { + const plugin = yield* PluginV2.Service + yield* plugin.add(ZenmuxPlugin) + const ignored = yield* plugin.trigger( + "provider.update", + {}, + { + provider: provider("openrouter", { + options: { + headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" }, + body: {}, + aisdk: { provider: {}, request: {} }, + }, + }), + cancel: false, + }, + ) + + expect(ignored.provider.options.headers).toEqual({ + "HTTP-Referer": "https://example.com/", + "X-Title": "custom-title", + }) + }), + ) +}) diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 48cb7b345..f3cea500c 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -91,7 +91,6 @@ "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", - "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", "@ai-sdk/xai": "3.0.82", diff --git a/packages/opencode/src/cli/cmd/debug/index.ts b/packages/opencode/src/cli/cmd/debug/index.ts index 6e2643f68..67ea51af6 100644 --- a/packages/opencode/src/cli/cmd/debug/index.ts +++ b/packages/opencode/src/cli/cmd/debug/index.ts @@ -16,6 +16,7 @@ import { SkillCommand } from "./skill" import { SnapshotCommand } from "./snapshot" import { AgentCommand } from "./agent" import { StartupCommand } from "./startup" +import { V2Command } from "./v2" export const DebugCommand = cmd({ command: "debug", @@ -31,6 +32,7 @@ export const DebugCommand = cmd({ .command(SnapshotCommand) .command(StartupCommand) .command(AgentCommand) + .command(V2Command) .command(InfoCommand) .command(PathsCommand) .command(WaitCommand) diff --git a/packages/opencode/src/cli/cmd/debug/v2.ts b/packages/opencode/src/cli/cmd/debug/v2.ts new file mode 100644 index 000000000..a4e69cc49 --- /dev/null +++ b/packages/opencode/src/cli/cmd/debug/v2.ts @@ -0,0 +1,40 @@ +import { EOL } from "os" +import { Effect, Layer, Option } from "effect" +import { Catalog } from "@opencode-ai/core/catalog" +import { effectCmd } from "../../effect-cmd" +import { PluginBoot } from "@/v2/plugin-boot" + +const layer = Catalog.defaultLayer.pipe(Layer.provide(PluginBoot.defaultLayer)) + +export const V2Command = effectCmd({ + command: "v2", + describe: "debug v2 catalog and built-in plugins", + instance: false, + handler: Effect.fn("Cli.debug.v2")(function* () { + const result = yield* Effect.gen(function* () { + const catalog = yield* Catalog.Service + + const providers = (yield* catalog.provider.available()).sort((a, b) => a.id.localeCompare(b.id)) + const all = (yield* catalog.provider.all()).sort((a, b) => a.id.localeCompare(b.id)) + return { + providers, + default: catalog.model + .default() + .pipe(Effect.map(Option.map((item) => item.id)), Effect.map(Option.getOrUndefined)), + small: Object.fromEntries( + yield* Effect.all( + all.map((provider) => + Effect.map( + catalog.model.small(provider.id), + (model) => [provider.id, Option.getOrUndefined(Option.map(model, (item) => item.id))] as const, + ), + ), + { concurrency: "unbounded" }, + ), + ), + } + }).pipe(Effect.provide(layer), Effect.orDie) + + process.stdout.write(JSON.stringify(result, null, 2) + EOL) + }), +}) 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 95d1b072f..b5e8e1028 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -64,7 +64,6 @@ import { DialogForkFromTimeline } from "./dialog-fork-from-timeline" import { DialogSessionRename } from "../../component/dialog-session-rename" import { Sidebar } from "./sidebar" import { SubagentFooter } from "./subagent-footer.tsx" -import { Flag } from "@opencode-ai/core/flag/flag" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import parsers from "../../../../../../parsers-config.ts" import * as Clipboard from "../../util/clipboard" @@ -1529,29 +1528,15 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess return ( - - - - - - - - + ) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index d4d28088d..ca87c40b7 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -24,7 +24,6 @@ import { InstanceState } from "@/effect/instance-state" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { isRecord } from "@/util/record" import { optionalOmitUndefined } from "@opencode-ai/core/schema" - import * as ProviderTransform from "./transform" import { ModelID, ProviderID } from "./schema" import { ModelStatus } from "./model-status" @@ -112,7 +111,8 @@ const BUNDLED_PROVIDERS: Record Promise<(opts: any) => BundledSDK> "@ai-sdk/vercel": () => import("@ai-sdk/vercel").then((m) => m.createVercel), "@ai-sdk/alibaba": () => import("@ai-sdk/alibaba").then((m) => m.createAlibaba), "gitlab-ai-provider": () => import("gitlab-ai-provider").then((m) => m.createGitLab), - "@ai-sdk/github-copilot": () => import("./sdk/copilot/copilot-provider").then((m) => m.createOpenaiCompatible), + "@ai-sdk/github-copilot": () => + import("@opencode-ai/core/github-copilot/copilot-provider").then((m) => m.createOpenaiCompatible), "venice-ai-sdk-provider": () => import("venice-ai-sdk-provider").then((m) => m.createVenice), } @@ -449,8 +449,14 @@ function custom(dep: CustomDep): Record { }), "google-vertex": Effect.fnUntraced(function* (provider: Info) { const env = yield* dep.env() + // models.dev advertises GOOGLE_VERTEX_PROJECT for Vertex; keep the wider + // Google Cloud project env names as fallbacks for existing ADC setups. const project = - provider.options?.project ?? env["GOOGLE_CLOUD_PROJECT"] ?? env["GCP_PROJECT"] ?? env["GCLOUD_PROJECT"] + provider.options?.project ?? + env["GOOGLE_VERTEX_PROJECT"] ?? + env["GOOGLE_CLOUD_PROJECT"] ?? + env["GCP_PROJECT"] ?? + env["GCLOUD_PROJECT"] const location = String( provider.options?.location ?? @@ -739,6 +745,7 @@ function custom(dep: CustomDep): Record { const auth = yield* dep.auth(input.id) const env = yield* dep.env() const accountId = env["CLOUDFLARE_ACCOUNT_ID"] || (auth?.type === "api" ? auth.metadata?.accountId : undefined) + // The Cloudflare auth prompt stores this value as gatewayId metadata. const gateway = env["CLOUDFLARE_GATEWAY_ID"] || (auth?.type === "api" ? auth.metadata?.gatewayId : undefined) if (!accountId || !gateway) { @@ -1097,11 +1104,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info { } } -const layer: Layer.Layer< - Service, - never, - Config.Service | Auth.Service | Plugin.Service | AppFileSystem.Service | Env.Service | ModelsDev.Service -> = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const fs = yield* AppFileSystem.Service @@ -1392,7 +1395,12 @@ const layer: Layer.Layer< for (const [modelID, model] of Object.entries(provider.models)) { model.api.id = model.api.id ?? model.id ?? modelID if ( - modelID === "gpt-5-chat-latest" || + // These chat aliases are invalid for the special handling in the + // built-in providers below, but custom providers may support them. + (modelID === "gpt-5-chat-latest" && + (providerID === ProviderID.openai || + providerID === ProviderID.githubCopilot || + providerID === ProviderID.openrouter)) || (providerID === ProviderID.openrouter && modelID === "openai/gpt-5-chat") ) delete provider.models[modelID] diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2.ts index 05da5b720..532ccce51 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2.ts @@ -1,10 +1,14 @@ import { HttpApi, OpenApi } from "effect/unstable/httpapi" import { MessageGroup } from "./v2/message" +import { ModelGroup } from "./v2/model" +import { ProviderGroup } from "./v2/provider" import { SessionGroup } from "./v2/session" export const V2Api = HttpApi.make("v2") .add(SessionGroup) .add(MessageGroup) + .add(ModelGroup) + .add(ProviderGroup) .annotateMerge( OpenApi.annotations({ title: "opencode experimental HttpApi", diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts new file mode 100644 index 000000000..35e7aeb85 --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts @@ -0,0 +1,24 @@ +import { ModelV2 } from "@opencode-ai/core/model" +import { Schema } from "effect" +import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "../../middleware/authorization" + +export const ModelGroup = HttpApiGroup.make("v2.model") + .add( + HttpApiEndpoint.get("models", "/api/model", { + success: Schema.Array(ModelV2.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "v2.model.list", + summary: "List v2 models", + description: "Retrieve available v2 models ordered by release date.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "v2 models", + description: "Experimental v2 model routes.", + }), + ) + .middleware(Authorization) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts new file mode 100644 index 000000000..6d9210088 --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts @@ -0,0 +1,38 @@ +import { ProviderV2 } from "@opencode-ai/core/provider" +import { Schema } from "effect" +import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { ApiNotFoundError } from "../../errors" +import { Authorization } from "../../middleware/authorization" + +export const ProviderGroup = HttpApiGroup.make("v2.provider") + .add( + HttpApiEndpoint.get("providers", "/api/provider", { + success: Schema.Array(ProviderV2.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "v2.provider.list", + summary: "List v2 providers", + description: "Retrieve active v2 AI providers so clients can show provider availability and configuration.", + }), + ), + ) + .add( + HttpApiEndpoint.get("provider", "/api/provider/:providerID", { + params: { providerID: ProviderV2.ID }, + success: ProviderV2.Info, + error: ApiNotFoundError, + }).annotateMerge( + OpenApi.annotations({ + identifier: "v2.provider.get", + summary: "Get v2 provider", + description: "Retrieve a single v2 AI provider so clients can inspect its availability and endpoint settings.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "v2 providers", + description: "Experimental v2 provider routes.", + }), + ) + .middleware(Authorization) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts index 231f1915b..3776f5c72 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/session.ts @@ -1,6 +1,6 @@ import { SessionID } from "@/session/schema" import { SessionMessage } from "@/v2/session-message" -import { Prompt } from "@/v2/session-prompt" +import { Prompt } from "@opencode-ai/core/session-prompt" import { SessionV2 } from "@/v2/session" import { Schema } from "effect" import { HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts index 55cb53458..b277e7701 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts @@ -1,6 +1,12 @@ +import { Catalog } from "@opencode-ai/core/catalog" import { SessionV2 } from "@/v2/session" import { Layer } from "effect" import { messageHandlers } from "./v2/message" +import { modelHandlers } from "./v2/model" +import { providerHandlers } from "./v2/provider" import { sessionHandlers } from "./v2/session" -export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers).pipe(Layer.provide(SessionV2.defaultLayer)) +export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers, modelHandlers, providerHandlers).pipe( + Layer.provide(Catalog.defaultLayer), + Layer.provide(SessionV2.defaultLayer), +) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts new file mode 100644 index 000000000..7eb1b310b --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts @@ -0,0 +1,12 @@ +import { Catalog } from "@opencode-ai/core/catalog" +import { Effect } from "effect" +import { HttpApiBuilder } from "effect/unstable/httpapi" +import { InstanceHttpApi } from "../../api" + +export const modelHandlers = HttpApiBuilder.group(InstanceHttpApi, "v2.model", (handlers) => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + + return handlers.handle("models", () => catalog.model.available()) + }), +) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts new file mode 100644 index 000000000..c19213f5b --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts @@ -0,0 +1,22 @@ +import { Catalog } from "@opencode-ai/core/catalog" +import { Effect } from "effect" +import { HttpApiBuilder } from "effect/unstable/httpapi" +import { InstanceHttpApi } from "../../api" +import { notFound } from "../../errors" + +export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "v2.provider", (handlers) => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + + return handlers + .handle("providers", () => catalog.provider.available()) + .handle( + "provider", + Effect.fn(function* (ctx) { + return yield* catalog.provider + .get(ctx.params.providerID) + .pipe(Effect.catchTag("CatalogV2.ProviderNotFound", () => Effect.fail(notFound("Provider not found")))) + }), + ) + }), +) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index c7990d1b3..c31545a3d 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -19,7 +19,6 @@ import { Bus } from "@/bus" import { Wildcard } from "@/util/wildcard" import { SessionID } from "@/session/schema" import { Auth } from "@/auth" -import { Installation } from "@/installation" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { EffectBridge } from "@/effect/bridge" import * as Option from "effect/Option" diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index c731239b6..9765175e9 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -23,7 +23,8 @@ import * as Log from "@opencode-ai/core/util/log" import { isRecord } from "@/util/record" import { SyncEvent } from "@/sync" import { SessionEvent } from "@/v2/session-event" -import { Modelv2 } from "@/v2/model" +import { ModelV2 } from "@opencode-ai/core/model" +import { ProviderV2 } from "@opencode-ai/core/provider" import * as DateTime from "effect/DateTime" import { RuntimeFlags } from "@/effect/runtime-flags" @@ -484,9 +485,9 @@ export const layer: Layer.Layer< sessionID: ctx.sessionID, agent: input.assistantMessage.agent, model: { - id: Modelv2.ID.make(ctx.model.id), - providerID: Modelv2.ProviderID.make(ctx.model.providerID), - variant: Modelv2.VariantID.make(input.assistantMessage.variant ?? "default"), + id: ModelV2.ID.make(ctx.model.id), + providerID: ProviderV2.ID.make(ctx.model.providerID), + variant: ModelV2.VariantID.make(input.assistantMessage.variant ?? "default"), }, snapshot: ctx.snapshot, timestamp: DateTime.makeUnsafe(Date.now()), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index cae0dd384..bc58fbdf3 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -53,8 +53,9 @@ import { EffectBridge } from "@/effect/bridge" import { RuntimeFlags } from "@/effect/runtime-flags" import { SyncEvent } from "@/sync" import { SessionEvent } from "@/v2/session-event" -import { Modelv2 } from "@/v2/model" -import { AgentAttachment, FileAttachment, ReferenceAttachment, Source } from "@/v2/session-prompt" +import { ModelV2 } from "@opencode-ai/core/model" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { AgentAttachment, FileAttachment, ReferenceAttachment, Source } from "@opencode-ai/core/session-prompt" import { Reference } from "@/reference/reference" import * as DateTime from "effect/DateTime" import { eq } from "@/storage/db" @@ -1143,9 +1144,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(info.time.created), model: { - id: Modelv2.ID.make(info.model.modelID), - providerID: Modelv2.ProviderID.make(info.model.providerID), - variant: Modelv2.VariantID.make(info.model.variant ?? "default"), + id: ModelV2.ID.make(info.model.modelID), + providerID: ProviderV2.ID.make(info.model.providerID), + variant: ModelV2.VariantID.make(info.model.variant ?? "default"), }, }) } diff --git a/packages/opencode/src/v2/model.ts b/packages/opencode/src/v2/model.ts deleted file mode 100644 index 56357ab40..000000000 --- a/packages/opencode/src/v2/model.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { withStatics } from "@opencode-ai/core/schema" -import { ModelStatus } from "@/provider/model-status" -import { Array, Context, Effect, HashMap, Layer, Option, Order, pipe, Schema } from "effect" -import { DateTimeUtcFromMillis } from "effect/Schema" - -export const ID = Schema.String.pipe(Schema.brand("Model.ID")) -export type ID = typeof ID.Type - -export const ProviderID = Schema.String.pipe( - Schema.brand("Model.ProviderID"), - withStatics((schema) => ({ - // Well-known providers - opencode: schema.make("opencode"), - anthropic: schema.make("anthropic"), - openai: schema.make("openai"), - google: schema.make("google"), - googleVertex: schema.make("google-vertex"), - githubCopilot: schema.make("github-copilot"), - amazonBedrock: schema.make("amazon-bedrock"), - azure: schema.make("azure"), - openrouter: schema.make("openrouter"), - mistral: schema.make("mistral"), - gitlab: schema.make("gitlab"), - })), -) -export type ProviderID = typeof ProviderID.Type - -export const VariantID = Schema.String.pipe(Schema.brand("VariantID")) -export type VariantID = typeof VariantID.Type - -// Grouping of models, eg claude opus, claude sonnet -export const Family = Schema.String.pipe(Schema.brand("Family")) -export type Family = typeof Family.Type - -const OpenAIResponses = Schema.Struct({ - type: Schema.Literal("openai/responses"), - url: Schema.String, - websocket: Schema.optional(Schema.Boolean), -}) - -const OpenAICompletions = Schema.Struct({ - type: Schema.Literal("openai/completions"), - url: Schema.String, - reasoning: Schema.Union([ - Schema.Struct({ - type: Schema.Literal("reasoning_content"), - }), - Schema.Struct({ - type: Schema.Literal("reasoning_details"), - }), - ]).pipe(Schema.optional), -}) -export type OpenAICompletions = typeof OpenAICompletions.Type - -const AnthropicMessages = Schema.Struct({ - type: Schema.Literal("anthropic/messages"), - url: Schema.String, -}) - -export const Endpoint = Schema.Union([OpenAIResponses, OpenAICompletions, AnthropicMessages]).pipe( - Schema.toTaggedUnion("type"), -) -export type Endpoint = typeof Endpoint.Type - -export const Capabilities = Schema.Struct({ - tools: Schema.Boolean, - // mime patterns, image, audio, video/*, text/* - input: Schema.String.pipe(Schema.Array), - output: Schema.String.pipe(Schema.Array), -}) -export type Capabilities = typeof Capabilities.Type - -export const Options = Schema.Struct({ - headers: Schema.Record(Schema.String, Schema.String), - body: Schema.Record(Schema.String, Schema.Any), -}) -export type Options = typeof Options.Type - -export const Cost = Schema.Struct({ - tier: Schema.Struct({ - type: Schema.Literal("context"), - size: Schema.Int, - }).pipe(Schema.optional), - input: Schema.Finite, - output: Schema.Finite, - cache: Schema.Struct({ - read: Schema.Finite, - write: Schema.Finite, - }), -}) - -export const Ref = Schema.Struct({ - id: ID, - providerID: ProviderID, - variant: VariantID, -}) -export type Ref = typeof Ref.Type - -export class Info extends Schema.Class("Model.Info")({ - id: ID, - providerID: ProviderID, - family: Family.pipe(Schema.optional), - name: Schema.String, - endpoint: Endpoint, - capabilities: Capabilities, - options: Schema.Struct({ - ...Options.fields, - variant: Schema.String.pipe(Schema.optional), - }), - variants: Schema.Struct({ - id: VariantID, - ...Options.fields, - }).pipe(Schema.Array), - time: Schema.Struct({ - released: DateTimeUtcFromMillis, - }), - cost: Cost.pipe(Schema.Array), - status: ModelStatus, - limit: Schema.Struct({ - context: Schema.Int, - input: Schema.Int.pipe(Schema.optional), - output: Schema.Int, - }), -}) {} - -export function parse(input: string): { providerID: ProviderID; modelID: ID } { - const [providerID, ...modelID] = input.split("/") - return { - providerID: ProviderID.make(providerID), - modelID: ID.make(modelID.join("/")), - } -} - -export interface Interface { - readonly get: (providerID: ProviderID, modelID: ID) => Effect.Effect> - readonly add: (model: Info) => Effect.Effect - readonly remove: (providerID: ProviderID, modelID: ID) => Effect.Effect - readonly all: () => Effect.Effect - readonly default: () => Effect.Effect> - readonly small: (provider: ProviderID) => Effect.Effect> -} - -export class Service extends Context.Service()("@opencode/v2/Model") {} - -export const layer = Layer.effect( - Service, - Effect.gen(function* () { - let models = HashMap.empty() - - function key(providerID: ProviderID, modelID: ID) { - return `${providerID}/${modelID}` - } - - const result: Interface = { - get: Effect.fn("V2Model.get")(function* (providerID, modelID) { - return HashMap.get(models, key(providerID, modelID)) - }), - - add: Effect.fn("V2Model.add")(function* (model) { - models = HashMap.set(models, key(model.providerID, model.id), model) - }), - - remove: Effect.fn("V2Model.remove")(function* (providerID, modelID) { - models = HashMap.remove(models, key(providerID, modelID)) - }), - - all: Effect.fn("V2Model.all")(function* () { - return pipe( - models, - HashMap.toValues, - Array.sortWith((item) => item.time.released.epochMilliseconds, Order.flip(Order.Number)), - ) - }), - - default: Effect.fn("V2Model.default")(function* () { - const all = yield* result.all() - return Option.fromUndefinedOr(all[0]) - }), - - small: Effect.fn("V2Model.small")(function* (providerID) { - const all = yield* result.all() - const match = all.find((model) => model.providerID === providerID && model.id.toLowerCase().includes("small")) - return Option.fromUndefinedOr(match) - }), - } - - return Service.of(result) - }), -) - -export const defaultLayer = layer - -export * as Modelv2 from "./model" diff --git a/packages/opencode/src/v2/plugin-boot.ts b/packages/opencode/src/v2/plugin-boot.ts new file mode 100644 index 000000000..d19872f2d --- /dev/null +++ b/packages/opencode/src/v2/plugin-boot.ts @@ -0,0 +1,50 @@ +export * as PluginBoot from "./plugin-boot" + +import { Npm } from "@opencode-ai/core/npm" +import { Effect, Layer } from "effect" +import { AuthV2 } from "@opencode-ai/core/auth" +import { Catalog } from "@opencode-ai/core/catalog" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { AuthPlugin } from "@opencode-ai/core/plugin/auth" +import { EnvPlugin } from "@opencode-ai/core/plugin/env" +import { ProviderPlugins } from "@opencode-ai/core/plugin/provider" +import { ModelsDevPlugin } from "./plugin/models-dev" + +type Plugin = { + id: PluginV2.ID + effect: Effect.Effect +} + +export const layer = Layer.effectDiscard( + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const plugin = yield* PluginV2.Service + const auth = yield* AuthV2.Service + const npm = yield* Npm.Service + + const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) { + yield* plugin.add({ + id: input.id, + effect: input.effect.pipe( + Effect.provideService(Catalog.Service, catalog), + Effect.provideService(AuthV2.Service, auth), + Effect.provideService(Npm.Service, npm), + ), + }) + }) + + yield* add(EnvPlugin) + yield* add(AuthPlugin) + for (const item of ProviderPlugins) { + yield* add(item) + } + yield* add(ModelsDevPlugin) + }), +) + +export const defaultLayer = layer.pipe( + Layer.provide(Catalog.defaultLayer), + Layer.provide(PluginV2.defaultLayer), + Layer.provide(Layer.orDie(AuthV2.defaultLayer)), + Layer.provide(Npm.defaultLayer), +) diff --git a/packages/opencode/src/v2/plugin/models-dev.ts b/packages/opencode/src/v2/plugin/models-dev.ts new file mode 100644 index 000000000..7c0e902c7 --- /dev/null +++ b/packages/opencode/src/v2/plugin/models-dev.ts @@ -0,0 +1,108 @@ +import { DateTime, Effect } from "effect" +import { Catalog } from "@opencode-ai/core/catalog" +import { ModelV2 } from "@opencode-ai/core/model" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { ModelsDev } from "@/provider/models" +import { PluginV2 } from "@opencode-ai/core/plugin" + +function released(date: string) { + const time = Date.parse(date) + return DateTime.makeUnsafe(Number.isFinite(time) ? time : 0) +} + +function cost(input: ModelsDev.Model["cost"]) { + const base = { + input: input?.input ?? 0, + output: input?.output ?? 0, + cache: { + read: input?.cache_read ?? 0, + write: input?.cache_write ?? 0, + }, + } + if (!input?.context_over_200k) return [base] + return [ + base, + { + tier: { + type: "context" as const, + size: 200_000, + }, + input: input.context_over_200k.input, + output: input.context_over_200k.output, + cache: { + read: input.context_over_200k.cache_read ?? 0, + write: input.context_over_200k.cache_write ?? 0, + }, + }, + ] +} + +function variants(model: ModelsDev.Model) { + return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => ({ + id: ModelV2.VariantID.make(id), + headers: { ...(item.provider?.headers ?? {}) }, + body: { ...(item.provider?.body ?? {}) }, + aisdk: { + provider: {}, + request: {}, + }, + })) +} + +export const ModelsDevPlugin = PluginV2.define({ + id: PluginV2.ID.make("models-dev"), + effect: Effect.gen(function* () { + const catalog = yield* Catalog.Service + const modelsDev = yield* ModelsDev.Service + for (const item of Object.values(yield* modelsDev.get())) { + const providerID = ProviderV2.ID.make(item.id) + yield* catalog.provider.update(providerID, (provider) => { + provider.name = item.name + provider.env = [...item.env] + provider.endpoint = item.npm + ? { + type: "aisdk", + package: item.npm, + url: item.api, + } + : { + type: "unknown", + } + }) + + for (const model of Object.values(item.models)) { + const modelID = ModelV2.ID.make(model.id) + yield* catalog.model + .update(providerID, modelID, (draft) => { + draft.name = model.name + draft.family = model.family ? ModelV2.Family.make(model.family) : undefined + draft.endpoint = model.provider?.npm + ? { + type: "aisdk", + package: model.provider?.npm, + url: model.provider.api, + } + : { + type: "unknown", + } + draft.capabilities = { + tools: model.tool_call, + input: [...(model.modalities?.input ?? [])], + output: [...(model.modalities?.output ?? [])], + } + draft.variants = variants(model) + draft.time.released = released(model.release_date) + draft.cost = cost(model.cost) + draft.status = model.status ?? "active" + draft.enabled = true + draft.limit = { + context: model.limit.context, + input: model.limit.input, + output: model.limit.output, + } + }) + .pipe(Effect.orDie) + } + } + }).pipe(Effect.provide(ModelsDev.defaultLayer)), +}) diff --git a/packages/opencode/src/v2/provider-parity-checklist.md b/packages/opencode/src/v2/provider-parity-checklist.md new file mode 100644 index 000000000..e3a599d8e --- /dev/null +++ b/packages/opencode/src/v2/provider-parity-checklist.md @@ -0,0 +1,95 @@ +# Unported Provider Logic Checklist + +This tracks legacy provider behavior from `packages/opencode/src/provider/provider.ts` that still needs to be ported into the v2 provider plugins under `packages/opencode/src/v2/plugin/provider/`. Keep entries checked only when v2 has equivalent behavior or when the item is intentionally skipped. + +## Provider Setup + +- [x] Cloudflare AI Gateway custom SDK construction with `createAiGateway` / `createUnified`. +- [x] Google Vertex authenticated `fetch` injection. +- [x] Amazon Bedrock AWS credential chain setup. +- [x] Amazon Bedrock bearer token setup. +- [x] SAP AI Core service key setup. + +## Provider Options + +- [x] Azure resource name resolution. +- [x] Azure missing-resource error. +- [x] Azure Cognitive Services baseURL resolution. +- [x] Cloudflare Workers AI account ID validation. +- [x] Cloudflare Workers AI account ID vars. +- [x] Cloudflare AI Gateway account ID validation. +- [x] Cloudflare AI Gateway gateway ID validation. +- [x] Cloudflare AI Gateway token validation. +- [x] Amazon Bedrock region precedence. +- [x] Amazon Bedrock profile precedence. +- [x] Amazon Bedrock endpoint precedence. +- [x] Google Vertex project resolution. +- [x] Google Vertex location resolution. +- [x] GitLab instance URL resolution. +- [x] GitLab token resolution. +- [x] GitLab AI gateway headers. +- [x] GitLab feature flags. +- [x] Opencode unauthenticated paid-model filtering. +- [x] Opencode public API key fallback. + +## Request Behavior + +- [x] Request timeout handling. +- [x] Chunk timeout handling. +- [x] SSE timeout wrapping. +- [x] OpenAI response item ID stripping. +- [x] Azure response item ID stripping. +- [x] OpenAI-compatible `includeUsage` defaulting. + +## Dynamic Models + +- [ ] GitLab workflow model discovery. + +## Model Filtering + +- [ ] Experimental alpha model filtering. +- [ ] Deprecated model filtering. +- [ ] Config whitelist filtering. +- [ ] Config blacklist filtering. +- [ ] `gpt-5-chat-latest` filtering. +- [ ] OpenRouter `openai/gpt-5-chat` filtering. + +## Default Models + +- [x] Configured default model selection. Replaced by explicit `Catalog.model.setDefault`. +- [SKIP] Recent-history default model selection — not porting to server-side v2 catalog. +- [x] Default model fallback sorting. Uses newest available model, not legacy hard-coded priority. + +## Small Models + +- [SKIP] Configured `small_model` selection — not porting config-driven selection to server-side v2 catalog. +- [x] Provider-specific small model priority. Replaced by cheapest output cost selection. +- [x] Opencode small model priority. Replaced by cheapest output cost selection. +- [x] GitHub Copilot small model priority. Replaced by cheapest output cost selection. +- [x] Amazon Bedrock region-aware small model selection. Replaced by cheapest output cost selection. + +## URL And Env Vars + +- [SKIP] BaseURL `${VAR}` interpolation — not porting generic URL templating; provider plugins should construct concrete URLs. +- [x] Azure `AZURE_RESOURCE_NAME` vars. Handled by Azure provider plugins. +- [x] Google Vertex vars. Handled by Google Vertex provider plugins. +- [x] Cloudflare Workers AI vars. Handled by Cloudflare Workers AI provider plugin. + +## Auth + +- [ ] Auth-derived provider API keys. +- [ ] OpenAI OAuth/API auth distinction. +- [ ] GitLab OAuth token selection. +- [ ] GitLab API token selection. +- [ ] Azure auth metadata resource name. +- [ ] Cloudflare auth metadata account ID. +- [ ] Cloudflare auth metadata gateway ID. + +## Config And Plugin Parity + +- [ ] Legacy plugin auth loader behavior. +- [ ] Config provider merge behavior. +- [ ] Config model merge behavior. +- [ ] Variant generation from model metadata. +- [ ] Config variant merge behavior. +- [ ] Config variant disable behavior. diff --git a/packages/opencode/src/v2/session-event.ts b/packages/opencode/src/v2/session-event.ts index fa211bd8c..1fd0f909d 100644 --- a/packages/opencode/src/v2/session-event.ts +++ b/packages/opencode/src/v2/session-event.ts @@ -1,12 +1,12 @@ import { SessionID } from "@/session/schema" import { NonNegativeInt } from "@opencode-ai/core/schema" import { EventV2 } from "./event" -import { FileAttachment, Prompt } from "./session-prompt" +import { FileAttachment, Prompt } from "@opencode-ai/core/session-prompt" import { Schema } from "effect" export { FileAttachment } -import { ToolOutput } from "./tool-output" -import { V2Schema } from "./schema" -import { Modelv2 } from "./model" +import { ToolOutput } from "@opencode-ai/core/tool-output" +import { V2Schema } from "@opencode-ai/core/v2-schema" +import { ModelV2 } from "@opencode-ai/core/model" export const Source = Schema.Struct({ start: NonNegativeInt, @@ -47,7 +47,7 @@ export const ModelSwitched = EventV2.define({ version: 1, schema: { ...Base, - model: Modelv2.Ref, + model: ModelV2.Ref, }, }) export type ModelSwitched = Schema.Schema.Type @@ -104,7 +104,7 @@ export namespace Step { schema: { ...Base, agent: Schema.String, - model: Modelv2.Ref, + model: ModelV2.Ref, snapshot: Schema.String.pipe(Schema.optional), }, }) diff --git a/packages/opencode/src/v2/session-message.ts b/packages/opencode/src/v2/session-message.ts index 62fc75fc8..fa7c299ae 100644 --- a/packages/opencode/src/v2/session-message.ts +++ b/packages/opencode/src/v2/session-message.ts @@ -1,10 +1,10 @@ import { Schema } from "effect" -import { Prompt } from "./session-prompt" +import { Prompt } from "@opencode-ai/core/session-prompt" import { SessionEvent } from "./session-event" import { EventV2 } from "./event" -import { ToolOutput } from "./tool-output" -import { V2Schema } from "./schema" -import { Modelv2 } from "./model" +import { ToolOutput } from "@opencode-ai/core/tool-output" +import { V2Schema } from "@opencode-ai/core/v2-schema" +import { ModelV2 } from "@opencode-ai/core/model" export const ID = EventV2.ID export type ID = Schema.Schema.Type @@ -26,7 +26,7 @@ export class AgentSwitched extends Schema.Class("Session.Message. export class ModelSwitched extends Schema.Class("Session.Message.ModelSwitched")({ ...Base, type: Schema.Literal("model-switched"), - model: Modelv2.Ref, + model: ModelV2.Ref, }) {} export class User extends Schema.Class("Session.Message.User")({ diff --git a/packages/opencode/src/v2/session.ts b/packages/opencode/src/v2/session.ts index f6084cb4c..97c31d39b 100644 --- a/packages/opencode/src/v2/session.ts +++ b/packages/opencode/src/v2/session.ts @@ -5,14 +5,15 @@ import { and, asc, desc, eq, gt, gte, isNull, like, lt, or, type SQL } from "@/s import * as Database from "@/storage/db" import { Context, DateTime, Effect, Layer, Option, Schema } from "effect" import { SessionMessage } from "./session-message" -import type { Prompt } from "./session-prompt" +import type { Prompt } from "@opencode-ai/core/session-prompt" import { EventV2 } from "./event" import { ProjectID } from "@/project/schema" import { SessionEvent } from "./session-event" -import { V2Schema } from "./schema" +import { V2Schema } from "@opencode-ai/core/v2-schema" import { optionalOmitUndefined } from "@opencode-ai/core/schema" -import { Modelv2 } from "./model" import { SyncEvent } from "@/sync" +import { ModelV2 } from "@opencode-ai/core/model" +import { ProviderV2 } from "@opencode-ai/core/provider" export const Delivery = Schema.Literals(["immediate", "deferred"]).annotate({ identifier: "Session.Delivery", @@ -28,7 +29,7 @@ export class Info extends Schema.Class("Session.Info")({ workspaceID: optionalOmitUndefined(WorkspaceID), path: optionalOmitUndefined(Schema.String), agent: optionalOmitUndefined(Schema.String), - model: Modelv2.Ref.pipe(optionalOmitUndefined), + model: ModelV2.Ref.pipe(optionalOmitUndefined), cost: Schema.Finite, tokens: Schema.Struct({ input: Schema.Finite, @@ -67,7 +68,7 @@ export class NotFoundError extends Schema.TaggedErrorClass()("Ses export interface Interface { readonly create: (input?: { agent?: string - model?: Modelv2.Ref + model?: ModelV2.Ref parentID?: SessionID workspaceID?: WorkspaceID }) => Effect.Effect @@ -111,10 +112,10 @@ export interface Interface { parentID: SessionID prompt: Prompt agent: string - model?: Modelv2.Ref + model?: ModelV2.Ref }) => Effect.Effect readonly switchAgent: (input: { sessionID: SessionID; agent: string }) => Effect.Effect - readonly switchModel: (input: { sessionID: SessionID; model: Modelv2.Ref }) => Effect.Effect + readonly switchModel: (input: { sessionID: SessionID; model: ModelV2.Ref }) => Effect.Effect readonly compact: (sessionID: SessionID) => Effect.Effect readonly wait: (sessionID: SessionID) => Effect.Effect } @@ -141,9 +142,9 @@ export const layer = Layer.effect( agent: row.agent ?? undefined, model: row.model ? { - id: Modelv2.ID.make(row.model.id), - providerID: Modelv2.ProviderID.make(row.model.providerID), - variant: Modelv2.VariantID.make(row.model.variant ?? "default"), + id: ModelV2.ID.make(row.model.id), + providerID: ProviderV2.ID.make(row.model.providerID), + variant: ModelV2.VariantID.make(row.model.variant ?? "default"), } : undefined, cost: row.cost, @@ -164,7 +165,7 @@ export const layer = Layer.effect( }) } - const result: Interface = { + const result = Service.of({ create: Effect.fn("V2Session.create")(function* (_input) { return {} as any }), @@ -306,7 +307,7 @@ export const layer = Layer.effect( }), subagent: Effect.fn("V2Session.subagent")(function* (input) { const parent = yield* result.get(input.parentID) - const session = yield* result.create({ + const child = yield* result.create({ agent: input.agent, model: input.model, parentID: input.parentID, @@ -314,11 +315,11 @@ export const layer = Layer.effect( }) yield* result.prompt({ prompt: input.prompt, - sessionID: session.id, + sessionID: child.id, }) yield* Effect.gen(function* () { - yield* result.wait(session.id) - const messages = yield* result.messages({ sessionID: session.id, order: "desc" }) + yield* result.wait(child.id) + const messages = yield* result.messages({ sessionID: child.id, order: "desc" }) const assistant = messages.find((msg) => msg.type === "assistant") if (!assistant) return const text = assistant.content.findLast((part) => part.type === "text") @@ -327,9 +328,9 @@ export const layer = Layer.effect( }), compact: Effect.fn("V2Session.compact")(function* (_sessionID) {}), wait: Effect.fn("V2Session.wait")(function* (_sessionID) {}), - } + }) - return Service.of(result) + return result }), ) diff --git a/packages/opencode/test/server/httpapi-exercise/index.ts b/packages/opencode/test/server/httpapi-exercise/index.ts index 0d6bec2df..293e2a344 100644 --- a/packages/opencode/test/server/httpapi-exercise/index.ts +++ b/packages/opencode/test/server/httpapi-exercise/index.ts @@ -593,6 +593,12 @@ const scenarios: Scenario[] = [ check(auth.test === undefined, "auth remove should delete provider from isolated auth file") }), ), + http.protected.get("/api/model", "v2.model.list").json(200, array), + http.protected.get("/api/provider", "v2.provider.list").json(200, array), + http.protected + .get("/api/provider/{providerID}", "v2.provider.get") + .at((ctx) => ({ path: route("/api/provider/{providerID}", { providerID: "missing" }), headers: ctx.headers() })) + .json(404, object, "status"), http.protected .get("/api/session", "v2.session.list") .at((ctx) => ({ path: "/api/session?roots=true", headers: ctx.headers() })) diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 8b686739d..3e5527761 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -20,7 +20,8 @@ import { MessageV2 } from "../../src/session/message-v2" import { Database } from "@/storage/db" import { SessionMessageTable, SessionTable } from "@/session/session.sql" import { SessionMessage } from "../../src/v2/session-message" -import { Modelv2 } from "../../src/v2/model" +import { ModelV2 } from "@opencode-ai/core/model" +import { ProviderV2 } from "@opencode-ai/core/provider" import * as DateTime from "effect/DateTime" import * as Log from "@opencode-ai/core/util/log" import { eq } from "drizzle-orm" @@ -110,9 +111,9 @@ const insertLegacyAssistantMessage = (sessionID: SessionIDType) => type: "assistant", agent: "build", model: { - id: Modelv2.ID.make("model"), - providerID: Modelv2.ProviderID.make("provider"), - variant: Modelv2.VariantID.make("default"), + id: ModelV2.ID.make("model"), + providerID: ProviderV2.ID.make("provider"), + variant: ModelV2.VariantID.make("default"), }, time: { created: DateTime.makeUnsafe(1) }, content: [], diff --git a/packages/opencode/test/v2/session-message-updater.test.ts b/packages/opencode/test/v2/session-message-updater.test.ts index 44ac031ed..180483937 100644 --- a/packages/opencode/test/v2/session-message-updater.test.ts +++ b/packages/opencode/test/v2/session-message-updater.test.ts @@ -2,7 +2,8 @@ import { expect, test } from "bun:test" import * as DateTime from "effect/DateTime" import { SessionID } from "../../src/session/schema" import { EventV2 } from "../../src/v2/event" -import { Modelv2 } from "../../src/v2/model" +import { ModelV2 } from "@opencode-ai/core/model" +import { ProviderV2 } from "@opencode-ai/core/provider" import { SessionEvent } from "../../src/v2/session-event" import { SessionMessageUpdater } from "../../src/v2/session-message-updater" @@ -18,9 +19,9 @@ test("step snapshots carry over to assistant messages", () => { timestamp: DateTime.makeUnsafe(1), agent: "build", model: { - id: Modelv2.ID.make("model"), - providerID: Modelv2.ProviderID.make("provider"), - variant: Modelv2.VariantID.make("default"), + id: ModelV2.ID.make("model"), + providerID: ProviderV2.ID.make("provider"), + variant: ModelV2.VariantID.make("default"), }, snapshot: "before", }, @@ -62,9 +63,9 @@ test("text ended populates assistant text content", () => { timestamp: DateTime.makeUnsafe(1), agent: "build", model: { - id: Modelv2.ID.make("model"), - providerID: Modelv2.ProviderID.make("provider"), - variant: Modelv2.VariantID.make("default"), + id: ModelV2.ID.make("model"), + providerID: ProviderV2.ID.make("provider"), + variant: ModelV2.VariantID.make("default"), }, }, } satisfies SessionEvent.Event) @@ -106,9 +107,9 @@ test("tool completion stores completed timestamp", () => { timestamp: DateTime.makeUnsafe(1), agent: "build", model: { - id: Modelv2.ID.make("model"), - providerID: Modelv2.ProviderID.make("provider"), - variant: Modelv2.VariantID.make("default"), + id: ModelV2.ID.make("model"), + providerID: ProviderV2.ID.make("provider"), + variant: ModelV2.VariantID.make("default"), }, }, } satisfies SessionEvent.Event) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 1b36c1513..e6e0c4638 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -197,6 +197,7 @@ import type { TuiSelectSessionResponses, TuiShowToastResponses, TuiSubmitPromptResponses, + V2ModelListResponses, V2SessionCompactResponses, V2SessionContextResponses, V2SessionListErrors, @@ -4375,11 +4376,48 @@ export class Session3 extends HeyApiClient { } } +export class Model extends HeyApiClient { + /** + * List v2 models + * + * Retrieve available v2 models ordered by release date. + */ + public list( + 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: "/api/model", + ...options, + ...params, + }) + } +} + export class V2 extends HeyApiClient { private _session?: Session3 get session(): Session3 { return (this._session ??= new Session3({ client: this.client })) } + + private _model?: Model + get model(): Model { + return (this._model ??= new Model({ client: this.client })) + } } export class Control extends HeyApiClient { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 35fb6bf81..99bbfd5ec 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3378,6 +3378,78 @@ export type SessionMessage = | SessionMessageAssistant | SessionMessageCompaction +export type ModelV2Info = { + id: string + providerID: string + family?: string + name: string + endpoint: + | { + type: "openai/responses" + url: string + websocket?: boolean + } + | { + type: "openai/completions" + url: string + reasoning?: + | { + type: "reasoning_content" + } + | { + type: "reasoning_details" + } + } + | { + type: "anthropic/messages" + url: string + } + capabilities: { + tools: boolean + input: Array + output: Array + } + options: { + headers: { + [key: string]: string + } + body: { + [key: string]: unknown + } + variant?: string + } + variants: Array<{ + id: string + headers: { + [key: string]: string + } + body: { + [key: string]: unknown + } + }> + time: { + released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" + } + cost: Array<{ + tier?: { + type: "context" + size: number + } + input: number + output: number + cache: { + read: number + write: number + } + }> + status: "alpha" | "beta" | "deprecated" | "active" + limit: { + context: number + input?: number + output: number + } +} + export type EventTuiToastShow1 = { id: string type: "tui.toast.show" @@ -6505,6 +6577,25 @@ export type V2SessionMessagesResponses = { export type V2SessionMessagesResponse2 = V2SessionMessagesResponses[keyof V2SessionMessagesResponses] +export type V2ModelListData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/api/model" +} + +export type V2ModelListResponses = { + /** + * Success + */ + 200: Array +} + +export type V2ModelListResponse = V2ModelListResponses[keyof V2ModelListResponses] + export type TuiAppendPromptData = { body?: { text: string diff --git a/specs/v2/provider-model.md b/specs/v2/provider-model.md new file mode 100644 index 000000000..fe5a98bdd --- /dev/null +++ b/specs/v2/provider-model.md @@ -0,0 +1,329 @@ +# Provider and Model Catalog + +## Provider Schema + +```ts +export const ID = Schema.String.pipe( + Schema.brand("ProviderV2.ID"), + withStatics((schema) => ({ + opencode: schema.make("opencode"), + anthropic: schema.make("anthropic"), + openai: schema.make("openai"), + google: schema.make("google"), + googleVertex: schema.make("google-vertex"), + githubCopilot: schema.make("github-copilot"), + amazonBedrock: schema.make("amazon-bedrock"), + azure: schema.make("azure"), + openrouter: schema.make("openrouter"), + mistral: schema.make("mistral"), + gitlab: schema.make("gitlab"), + })), +) +export type ID = typeof ID.Type + +const OpenAIResponses = Schema.Struct({ + type: Schema.Literal("openai/responses"), + url: Schema.String, + websocket: Schema.optional(Schema.Boolean), +}) + +const OpenAICompletions = Schema.Struct({ + type: Schema.Literal("openai/completions"), + url: Schema.String, + reasoning: Schema.Union([ + Schema.Struct({ + type: Schema.Literal("reasoning_content"), + }), + Schema.Struct({ + type: Schema.Literal("reasoning_details"), + }), + ]).pipe(Schema.optional), +}) +export type OpenAICompletions = typeof OpenAICompletions.Type + +const AISDK = Schema.Struct({ + type: Schema.Literal("aisdk"), + package: Schema.String, +}) + +const AnthropicMessages = Schema.Struct({ + type: Schema.Literal("anthropic/messages"), + url: Schema.String, +}) + +const UnknownEndpoint = Schema.Struct({ + type: Schema.Literal("unknown"), +}) + +export const Endpoint = Schema.Union([UnknownEndpoint, OpenAIResponses, OpenAICompletions, AnthropicMessages, AISDK]).pipe( + Schema.toTaggedUnion("type"), +) +export type Endpoint = typeof Endpoint.Type + +export const Options = Schema.Struct({ + headers: Schema.Record(Schema.String, Schema.String), + body: Schema.Record(Schema.String, Schema.Any), +}) +export type Options = typeof Options.Type + +export class Info extends Schema.Class("ProviderV2.Info")({ + id: ID, + name: Schema.String, + enabled: Schema.Boolean, + env: Schema.String.pipe(Schema.Array), + endpoint: Endpoint, + options: Options, +}) { + static empty(providerID: ID) { + return new Info({ + id: providerID, + name: providerID, + enabled: false, + env: [], + endpoint: { + type: "unknown", + }, + options: { + headers: {}, + body: {}, + }, + }) + } +} + +export class NotFound extends Schema.TaggedErrorClass("ProviderV2.NotFound")("ProviderV2.NotFound", { + providerID: ID, +}) {} +``` + +## Model Schema + +```ts +export const ID = Schema.String.pipe(Schema.brand("ModelV2.ID")) +export type ID = typeof ID.Type + +export const VariantID = Schema.String.pipe(Schema.brand("VariantID")) +export type VariantID = typeof VariantID.Type + +export const Family = Schema.String.pipe(Schema.brand("Family")) +export type Family = typeof Family.Type + +export const Capabilities = Schema.Struct({ + tools: Schema.Boolean, + input: Schema.String.pipe(Schema.Array), + output: Schema.String.pipe(Schema.Array), +}) +export type Capabilities = typeof Capabilities.Type + +export const Variant = Schema.Struct({ + id: VariantID, + ...ProviderV2.Options.fields, +}) +export type Variant = typeof Variant.Type + +export const Cost = Schema.Struct({ + tier: Schema.Struct({ + type: Schema.Literal("context"), + size: Schema.Int, + }).pipe(Schema.optional), + input: Schema.Finite, + output: Schema.Finite, + cache: Schema.Struct({ + read: Schema.Finite, + write: Schema.Finite, + }), +}) +export type Cost = typeof Cost.Type + +export const Limit = Schema.Struct({ + context: Schema.Int, + input: Schema.Int.pipe(Schema.optional), + output: Schema.Int, +}) +export type Limit = typeof Limit.Type + +export const Ref = Schema.Struct({ + id: ID, + providerID: ProviderV2.ID, + variant: VariantID, +}) +export type Ref = typeof Ref.Type + +export class Info extends Schema.Class("ModelV2.Info")({ + id: ID, + providerID: ProviderV2.ID, + family: Family.pipe(Schema.optional), + name: Schema.String, + endpoint: ProviderV2.Endpoint, + options: Schema.Struct({ + ...ProviderV2.Options.fields, + variant: Schema.String.pipe(Schema.optional), + }), + capabilities: Capabilities, + variants: Variant.pipe(Schema.Array), + time: Schema.Struct({ + released: DateTimeUtcFromMillis, + }), + cost: Cost.pipe(Schema.Array), + status: Schema.Literals(["alpha", "beta", "deprecated", "active"]), + limit: Limit, +}) { + static empty(providerID: ProviderV2.ID, modelID: ID) { + return new Info({ + id: modelID, + providerID, + name: modelID, + endpoint: { + type: "unknown", + }, + capabilities: { + tools: false, + input: [], + output: [], + }, + options: { + headers: {}, + body: {}, + }, + variants: [], + time: { + released: DateTime.makeUnsafe(0), + }, + cost: [], + status: "active", + limit: { + context: 0, + output: 0, + }, + }) + } +} + +``` + +## Catalog Interface + +```ts +export interface Interface { + readonly provider: { + readonly get: (providerID: ProviderV2.ID) => Effect.Effect> + readonly update: (providerID: ProviderV2.ID, fn: (provider: Draft) => void) => Effect.Effect + readonly remove: (providerID: ProviderV2.ID) => Effect.Effect + readonly all: () => Effect.Effect + readonly available: () => Effect.Effect + } + + readonly model: { + readonly get: (providerID: ProviderV2.ID, modelID: ModelV2.ID) => Effect.Effect> + readonly update: ( + providerID: ProviderV2.ID, + modelID: ModelV2.ID, + fn: (model: Draft) => void, + ) => Effect.Effect + readonly remove: (providerID: ProviderV2.ID, modelID: ModelV2.ID) => Effect.Effect + readonly all: () => Effect.Effect + readonly available: () => Effect.Effect + readonly default: () => Effect.Effect> + readonly small: (providerID: ProviderV2.ID) => Effect.Effect> + } +} +``` + +`ProviderV2.Info.enabled` is stored provider state. Provider plugins set this field after checking env, auth, config, or provider-specific availability. + +`ProviderV2.Endpoint` includes `{ type: "unknown" }`. `CatalogV2.model.get()` and `CatalogV2.model.all()` resolve `unknown` endpoints from the provider before returning models. + +Model storage is nested by provider because model ids are only unique within a provider. + +```ts +type ProviderRecord = { + provider: ProviderV2.Info + models: HashMap.HashMap +} + +let records = HashMap.empty() +``` + +`ModelV2.Info` does not have an `enabled` field. Model availability is derived by `CatalogV2.model.available()` from provider state and model status. + +```ts +const available = provider.enabled && model.status !== "deprecated" +``` + +## Plugin Interface + +```ts +export type Definition = Effect.Effect<{ + readonly order: number + readonly hooks: HookFunctions +}, never, R> + +export interface Interface { + readonly add: (input: { + id: ID + definition: Definition + }) => Effect.Effect + + readonly remove: (id: ID) => Effect.Effect + + readonly trigger: ( + name: Name, + input: HookInput, + ) => Effect.Effect> +} +``` + +## Plugin Order + +```ts +export const Order = { + modelsDev: 0, + env: 10, + auth: 20, + provider: 30, + config: 40, + discovery: 50, +} as const +``` + +## Built-In Plugins + +```ts +export const ModelsDevPlugin: PluginV2.Definition + +export const EnvPlugin: PluginV2.Definition + +export const AuthPlugin: PluginV2.Definition + +export const ConfigPlugin: PluginV2.Definition + +export const AnthropicPlugin: PluginV2.Definition + +export const OpenRouterPlugin: PluginV2.Definition + +export const AmazonBedrockPlugin: PluginV2.Definition + +export const GoogleVertexPlugin: PluginV2.Definition + +export const GitLabPlugin: PluginV2.Definition + +export const GitLabDiscoveryPlugin: PluginV2.Definition +``` + +## Plugin Hooks + +```ts +export type Hooks = { + init: {} + + "provider.update": { + provider: Draft + cancel: boolean + } + + "model.update": { + model: Draft + cancel: boolean + } +} +``` From eed0eddc638ec1b3acc7d5252fd6ce85811943cd Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 20:14:40 +0530 Subject: [PATCH 258/378] refactor(flags): route session workspaces through runtime flags (#27335) --- packages/opencode/src/effect/runtime-flags.ts | 1 + packages/opencode/src/session/session.ts | 13 ++++++----- .../test/effect/runtime-flags.test.ts | 1 + packages/opencode/test/preload.ts | 1 + .../opencode/test/server/session-list.test.ts | 22 +++++++++++-------- .../opencode/test/session/session.test.ts | 19 ++++++++++++---- 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/packages/opencode/src/effect/runtime-flags.ts b/packages/opencode/src/effect/runtime-flags.ts index b1b8ab25a..4d184c43b 100644 --- a/packages/opencode/src/effect/runtime-flags.ts +++ b/packages/opencode/src/effect/runtime-flags.ts @@ -23,6 +23,7 @@ export class Service extends ConfigService.Service()("@opencode/Runtime experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"), experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"), experimentalEventSystem: enabledByExperimental("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"), + experimentalWorkspaces: enabledByExperimental("OPENCODE_EXPERIMENTAL_WORKSPACES"), client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")), }) {} diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index df173e895..edd4fe119 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -4,7 +4,6 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { Decimal } from "decimal.js" import { type ProviderMetadata, type LanguageModelUsage } from "ai" -import { Flag } from "@opencode-ai/core/flag/flag" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { Database } from "@/storage/db" @@ -38,6 +37,7 @@ import { Permission } from "@/permission" import { Global } from "@opencode-ai/core/global" import { Effect, Layer, Option, Context, Schema, Types } from "effect" import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema" +import { RuntimeFlags } from "@/effect/runtime-flags" const log = Log.create({ service: "session" }) @@ -507,12 +507,13 @@ export type Patch = Types.DeepMutable["dat const db = (fn: (d: Parameters[0] extends (trx: infer D) => any ? D : never) => T) => Effect.sync(() => Database.use(fn)) -export const layer: Layer.Layer = Layer.effect( +export const layer: Layer.Layer = Layer.effect( Service, Effect.gen(function* () { const bus = yield* Bus.Service const storage = yield* Storage.Service const sync = yield* SyncEvent.Service + const flags = yield* RuntimeFlags.Service const createNext = Effect.fn("Session.createNext")(function* (input: { id?: SessionID @@ -550,7 +551,7 @@ export const layer: Layer.Layer { expect(flags.experimentalLspTool).toBe(true) expect(flags.experimentalPlanMode).toBe(true) expect(flags.experimentalEventSystem).toBe(true) + expect(flags.experimentalWorkspaces).toBe(true) expect(flags.client).toBe("desktop") }), ) diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts index 6447c2fe9..24b804819 100644 --- a/packages/opencode/test/preload.ts +++ b/packages/opencode/test/preload.ts @@ -35,6 +35,7 @@ process.env["XDG_CONFIG_HOME"] = path.join(dir, "config") process.env["XDG_STATE_HOME"] = path.join(dir, "state") process.env["OPENCODE_MODELS_PATH"] = path.join(import.meta.dir, "tool", "fixtures", "models-api.json") process.env["OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"] = "true" +process.env["OPENCODE_EXPERIMENTAL_WORKSPACES"] = "true" // Set test home directory to isolate tests from user's actual home directory // This prevents tests from picking up real user configs/skills from ~/.claude/skills diff --git a/packages/opencode/test/server/session-list.test.ts b/packages/opencode/test/server/session-list.test.ts index e5dc72546..1bd3c6647 100644 --- a/packages/opencode/test/server/session-list.test.ts +++ b/packages/opencode/test/server/session-list.test.ts @@ -1,19 +1,28 @@ import { afterEach, describe, expect } from "bun:test" -import { Effect } from "effect" +import { Effect, Layer } from "effect" import { Session as SessionNs } from "@/session/session" import * as Log from "@opencode-ai/core/util/log" import { disposeAllInstances, provideInstance, TestInstance } from "../fixture/fixture" -import { Flag } from "@opencode-ai/core/flag/flag" import { mkdir } from "fs/promises" import path from "path" import { Database } from "@/storage/db" import { SessionTable } from "@/session/session.sql" import { eq } from "drizzle-orm" import { testEffect } from "../lib/effect" +import { Bus } from "@/bus" +import { Storage } from "@/storage/storage" +import { SyncEvent } from "@/sync" +import { RuntimeFlags } from "@/effect/runtime-flags" void Log.init({ print: false }) -const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES -const it = testEffect(SessionNs.defaultLayer) +const it = testEffect( + SessionNs.layer.pipe( + Layer.provide(Bus.layer), + Layer.provide(Storage.defaultLayer), + Layer.provide(SyncEvent.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: false })), + ), +) const withSession = (input?: Parameters[0]) => Effect.acquireRelease( @@ -22,7 +31,6 @@ const withSession = (input?: Parameters[0]) => ) afterEach(async () => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces await disposeAllInstances() }) @@ -31,7 +39,6 @@ describe("session.list", () => { "does not filter by directory when directory is omitted", () => Effect.gen(function* () { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false const test = yield* TestInstance yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode"), { recursive: true })) yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) @@ -60,7 +67,6 @@ describe("session.list", () => { "filters by directory when directory is provided", () => Effect.gen(function* () { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false const test = yield* TestInstance yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode"), { recursive: true })) yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) @@ -91,7 +97,6 @@ describe("session.list", () => { "filters by path and ignores directory when path is provided", () => Effect.gen(function* () { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false const test = yield* TestInstance yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode", "src", "deep"), { recursive: true }), @@ -129,7 +134,6 @@ describe("session.list", () => { "falls back to directory when filtering legacy sessions without path", () => Effect.gen(function* () { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false const test = yield* TestInstance yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode", "src"), { recursive: true }), diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index ada55d134..63920d218 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -3,16 +3,29 @@ import { Deferred, Effect, Exit, Layer } from "effect" import { Session as SessionNs } from "@/session/session" import { GlobalBus, type GlobalEvent } from "../../src/bus/global" import * as Log from "@opencode-ai/core/util/log" -import { Flag } from "@opencode-ai/core/flag/flag" import { MessageV2 } from "../../src/session/message-v2" import { MessageID, PartID, type SessionID } from "../../src/session/schema" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" +import { Bus } from "@/bus" +import { Storage } from "@/storage/storage" +import { SyncEvent } from "@/sync" +import { RuntimeFlags } from "@/effect/runtime-flags" void Log.init({ print: false }) -const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect( + Layer.mergeAll( + SessionNs.layer.pipe( + Layer.provide(Bus.layer), + Layer.provide(Storage.defaultLayer), + Layer.provide(SyncEvent.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: false })), + ), + CrossSpawnSpawner.defaultLayer, + ), +) const awaitDeferred = (deferred: Deferred.Deferred, message: string) => Effect.race( @@ -56,8 +69,6 @@ describe("session.created event", () => { it.instance("session.created event should be emitted before session.updated", () => Effect.gen(function* () { - if (Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return - const session = yield* SessionNs.Service const events: string[] = [] const received = yield* Deferred.make() From 8d5aa584b451dc7a815659d6d335cc3dd6961536 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 10:45:43 -0400 Subject: [PATCH 259/378] test(workspace): effectify sync start coverage (#27338) --- .../test/control-plane/workspace.test.ts | 63 +++++++++++-------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/packages/opencode/test/control-plane/workspace.test.ts b/packages/opencode/test/control-plane/workspace.test.ts index 3c4837e31..adac51fe5 100644 --- a/packages/opencode/test/control-plane/workspace.test.ts +++ b/packages/opencode/test/control-plane/workspace.test.ts @@ -16,13 +16,14 @@ import { ProjectID } from "@/project/schema" import { ProjectTable } from "@/project/project.sql" import { Instance } from "@/project/instance" import { WithInstance } from "../../src/project/with-instance" +import { InstanceRef } from "@/effect/instance-ref" import { Session as SessionNs } from "@/session/session" import { SessionID } from "@/session/schema" import { SessionTable } from "@/session/session.sql" import { SyncEvent } from "@/sync" import { EventSequenceTable } from "@/sync/event.sql" import { resetDatabase } from "../fixture/db" -import { disposeAllInstances, provideTmpdirInstance, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, provideTmpdirInstance, TestInstance, tmpdir } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { registerAdapter } from "../../src/control-plane/adapters" import { WorkspaceID } from "../../src/control-plane/schema" @@ -105,7 +106,7 @@ afterEach(async () => { async function withInstance(fn: (dir: string) => T | Promise) { await using tmp = await tmpdir({ git: true }) - return WithInstance.provide({ + return await WithInstance.provide({ directory: tmp.path, fn: () => fn(tmp.path), }) @@ -994,31 +995,43 @@ describe("workspace sync state", () => { }) }) - test("startWorkspaceSyncing starts all workspaces", async () => { - await withInstance(async (dir) => { - const firstType = unique("first") - const secondType = unique("second") - const first = workspaceInfo(Instance.project.id, firstType) - const second = workspaceInfo(Instance.project.id, secondType) - await fs.mkdir(path.join(dir, "first"), { recursive: true }) - await fs.mkdir(path.join(dir, "second"), { recursive: true }) - insertWorkspace(first) - insertWorkspace(second) - registerAdapter(Instance.project.id, firstType, localAdapter(path.join(dir, "first")).adapter) - registerAdapter(Instance.project.id, secondType, localAdapter(path.join(dir, "second")).adapter) + it.instance( + "startWorkspaceSyncing starts all workspaces", + () => + Effect.gen(function* () { + const { directory: dir } = yield* TestInstance + const instance = yield* InstanceRef + if (!instance) return yield* Effect.die(new Error("missing test instance")) + const workspace = yield* Workspace.Service + const projectID = instance.project.id + const firstType = unique("first") + const secondType = unique("second") + const first = workspaceInfo(projectID, firstType) + const second = workspaceInfo(projectID, secondType) + yield* Effect.promise(() => fs.mkdir(path.join(dir, "first"), { recursive: true })) + yield* Effect.promise(() => fs.mkdir(path.join(dir, "second"), { recursive: true })) + yield* Effect.sync(() => { + insertWorkspace(first) + insertWorkspace(second) + registerAdapter(projectID, firstType, localAdapter(path.join(dir, "first")).adapter) + registerAdapter(projectID, secondType, localAdapter(path.join(dir, "second")).adapter) + }) + yield* Effect.addFinalizer(() => + Effect.all([workspace.remove(first.id), workspace.remove(second.id)], { discard: true }).pipe(Effect.ignore), + ) - startWorkspaceSyncing(Instance.project.id) + yield* workspace.startWorkspaceSyncing(projectID) - await eventually(() => - workspaceStatus().then((status) => { - expect(status.find((item) => item.workspaceID === first.id)?.status).toBe("connected") - expect(status.find((item) => item.workspaceID === second.id)?.status).toBe("connected") - }), - ) - await removeWorkspace(first.id) - await removeWorkspace(second.id) - }) - }) + yield* eventuallyEffect( + Effect.gen(function* () { + const status = yield* workspace.status() + expect(status.find((item) => item.workspaceID === first.id)?.status).toBe("connected") + expect(status.find((item) => item.workspaceID === second.id)?.status).toBe("connected") + }), + ) + }), + { git: true }, + ) test("local start reports error when the target directory is missing", async () => { await withInstance(async (dir) => { From 766318a4cf1f972309eed9cca1532a22d365b3cf Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 10:46:14 -0400 Subject: [PATCH 260/378] effect(snapshot): migrate to AppProcess.run (#27189) --- packages/opencode/src/snapshot/index.ts | 56 ++++++++++++++++++------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 51fd267d5..70b034730 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -2,7 +2,7 @@ import { Cause, Duration, Effect, Layer, Schedule, Schema, Semaphore, Context, S import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { AppProcess } from "@opencode-ai/core/process" import { InstanceState } from "@/effect/instance-state" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Hash } from "@opencode-ai/core/util/hash" @@ -58,12 +58,12 @@ export class Service extends Context.Service()("@opencode/Sn export const layer: Layer.Layer< Service, never, - AppFileSystem.Service | ChildProcessSpawner.ChildProcessSpawner | Config.Service + AppFileSystem.Service | AppProcess.Service | Config.Service > = Layer.effect( Service, Effect.gen(function* () { const fs = yield* AppFileSystem.Service - const spawner = yield* ChildProcessSpawner.ChildProcessSpawner + const appProcess = yield* AppProcess.Service const config = yield* Config.Service const locks = new Map() @@ -90,18 +90,20 @@ export const layer: Layer.Layer< const enc = new TextEncoder() const feed = (list: string[]) => Stream.make(enc.encode(list.join("\0") + "\0")) - const git = Effect.fnUntraced( + const gitWithStdin = Effect.fnUntraced( function* ( cmd: string[], - opts?: { cwd?: string; env?: Record; stdin?: ChildProcess.CommandInput }, + opts: { cwd?: string; env?: Record; stdin: ChildProcess.CommandInput }, ) { + // stdin-feed calls still need raw spawn — AppProcess.run does not yet + // expose a stdin Stream API. Tracked as future AppProcess helper. const proc = ChildProcess.make("git", cmd, { - cwd: opts?.cwd, - env: opts?.env, + cwd: opts.cwd, + env: opts.env, extendEnv: true, - stdin: opts?.stdin, + stdin: opts.stdin, }) - const handle = yield* spawner.spawn(proc) + const handle = yield* appProcess.spawn(proc) const [text, stderr] = yield* Effect.all( [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], { concurrency: 2 }, @@ -119,9 +121,33 @@ export const layer: Layer.Layer< ), ) + const git = Effect.fnUntraced( + function* (cmd: string[], opts?: { cwd?: string; env?: Record }) { + const result = yield* appProcess.run( + ChildProcess.make("git", cmd, { + cwd: opts?.cwd, + env: opts?.env, + extendEnv: true, + }), + ) + return { + code: ChildProcessSpawner.ExitCode(result.exitCode), + text: result.stdout.toString("utf8"), + stderr: result.stderr.toString("utf8"), + } satisfies GitResult + }, + Effect.catch((err) => + Effect.succeed({ + code: ChildProcessSpawner.ExitCode(1), + text: "", + stderr: err instanceof Error ? err.message : String(err), + }), + ), + ) + const ignore = Effect.fnUntraced(function* (files: string[]) { if (!files.length) return new Set() - const check = yield* git( + const check = yield* gitWithStdin( [ ...quote, "--git-dir", @@ -144,7 +170,7 @@ export const layer: Layer.Layer< const drop = Effect.fnUntraced(function* (files: string[]) { if (!files.length) return - yield* git( + yield* gitWithStdin( [ ...cfg, ...args(["rm", "--cached", "-f", "--ignore-unmatch", "--pathspec-from-file=-", "--pathspec-file-nul"]), @@ -158,7 +184,7 @@ export const layer: Layer.Layer< const stage = Effect.fnUntraced(function* (files: string[]) { if (!files.length) return - const result = yield* git( + const result = yield* gitWithStdin( [...cfg, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])], { cwd: state.directory, @@ -565,12 +591,14 @@ export const layer: Layer.Layer< }) if (!refs.length) return new Map() + // cat-file --batch is a stdin-feed call — kept on raw spawn + // until AppProcess.run exposes a stdin Stream API. 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 handle = yield* appProcess.spawn(proc) const [out, err] = yield* Effect.all( [Stream.mkUint8Array(handle.stdout), Stream.mkString(Stream.decodeText(handle.stderr))], { concurrency: 2 }, @@ -767,7 +795,7 @@ export const layer: Layer.Layer< ) export const defaultLayer = layer.pipe( - Layer.provide(CrossSpawnSpawner.defaultLayer), + Layer.provide(AppProcess.defaultLayer), Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Config.defaultLayer), ) From 5b5376a3fa86aa055d42193697981950c8d0c006 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 14:47:40 +0000 Subject: [PATCH 261/378] chore: generate --- packages/core/src/plugin/provider/azure.ts | 21 +- packages/core/src/plugin/provider/gitlab.ts | 3 +- .../core/src/plugin/provider/google-vertex.ts | 25 +- packages/core/src/plugin/provider/opencode.ts | 2 +- .../core/src/plugin/provider/sap-ai-core.ts | 6 +- .../v2/plugin/provider-amazon-bedrock.test.ts | 5 +- packages/opencode/src/session/session.ts | 10 +- packages/opencode/src/snapshot/index.ts | 1325 ++++++++--------- packages/sdk/js/src/v2/gen/sdk.gen.ts | 57 +- packages/sdk/js/src/v2/gen/types.gen.ts | 141 +- packages/sdk/openapi.json | 639 ++++++++ specs/v2/provider-model.md | 33 +- 12 files changed, 1544 insertions(+), 723 deletions(-) diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts index 86c3eb924..6c29a1610 100644 --- a/packages/core/src/plugin/provider/azure.ts +++ b/packages/core/src/plugin/provider/azure.ts @@ -24,7 +24,11 @@ export const AzurePlugin = PluginV2.define({ "aisdk.sdk": Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/azure") return if (evt.model.providerID === ProviderV2.ID.azure) { - if (!evt.options.resourceName && !evt.options.baseURL && (evt.model.endpoint.type !== "aisdk" || !evt.model.endpoint.url)) { + if ( + !evt.options.resourceName && + !evt.options.baseURL && + (evt.model.endpoint.type !== "aisdk" || !evt.model.endpoint.url) + ) { throw new Error( "AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it", ) @@ -35,11 +39,7 @@ export const AzurePlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.azure) return - evt.language = selectLanguage( - evt.sdk, - evt.model.apiID, - Boolean(evt.options.useCompletionUrls), - ) + evt.language = selectLanguage(evt.sdk, evt.model.apiID, Boolean(evt.options.useCompletionUrls)) }), } }), @@ -52,15 +52,12 @@ export const AzureCognitiveServicesPlugin = PluginV2.define({ "provider.update": Effect.fn(function* (evt) { if (evt.provider.id !== ProviderV2.ID.make("azure-cognitive-services")) return const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME - if (resourceName) evt.provider.options.aisdk.provider.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai` + if (resourceName) + evt.provider.options.aisdk.provider.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai` }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return - evt.language = selectLanguage( - evt.sdk, - evt.model.apiID, - Boolean(evt.options.useCompletionUrls), - ) + evt.language = selectLanguage(evt.sdk, evt.model.apiID, Boolean(evt.options.useCompletionUrls)) }), } }), diff --git a/packages/core/src/plugin/provider/gitlab.ts b/packages/core/src/plugin/provider/gitlab.ts index be923e7cb..226f5a45e 100644 --- a/packages/core/src/plugin/provider/gitlab.ts +++ b/packages/core/src/plugin/provider/gitlab.ts @@ -32,7 +32,8 @@ export const GitLabPlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.gitlab) return - const featureFlags = typeof evt.options.featureFlags === "object" && evt.options.featureFlags ? evt.options.featureFlags : {} + const featureFlags = + typeof evt.options.featureFlags === "object" && evt.options.featureFlags ? evt.options.featureFlags : {} if (evt.model.apiID.startsWith("duo-workflow-")) { const gitlab = yield* Effect.promise(() => import("gitlab-ai-provider")).pipe(Effect.orDie) const workflowRef = diff --git a/packages/core/src/plugin/provider/google-vertex.ts b/packages/core/src/plugin/provider/google-vertex.ts index f22f79f45..0c335df93 100644 --- a/packages/core/src/plugin/provider/google-vertex.ts +++ b/packages/core/src/plugin/provider/google-vertex.ts @@ -15,7 +15,13 @@ function resolveProject(options: Record) { } function resolveLocation(options: Record) { - return options.location ?? process.env.GOOGLE_VERTEX_LOCATION ?? process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "us-central1" + return ( + options.location ?? + process.env.GOOGLE_VERTEX_LOCATION ?? + process.env.GOOGLE_CLOUD_LOCATION ?? + process.env.VERTEX_LOCATION ?? + "us-central1" + ) } function vertexEndpoint(location: string) { @@ -60,7 +66,10 @@ export const GoogleVertexPlugin = PluginV2.define({ if (evt.provider.endpoint.type === "aisdk" && evt.provider.endpoint.url) { evt.provider.endpoint.url = replaceVertexVars(evt.provider.endpoint.url, project, location) } - if (evt.provider.endpoint.type === "aisdk" && evt.provider.endpoint.package.includes("@ai-sdk/openai-compatible")) { + if ( + evt.provider.endpoint.type === "aisdk" && + evt.provider.endpoint.package.includes("@ai-sdk/openai-compatible") + ) { evt.provider.options.aisdk.provider.fetch = authFetch(evt.provider.options.aisdk.provider.fetch) } }), @@ -95,8 +104,16 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({ return { "provider.update": Effect.fn(function* (evt) { if (evt.provider.id !== ProviderV2.ID.make("google-vertex-anthropic")) return - const project = evt.provider.options.aisdk.provider.project ?? process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCP_PROJECT ?? process.env.GCLOUD_PROJECT - const location = evt.provider.options.aisdk.provider.location ?? process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "global" + const project = + evt.provider.options.aisdk.provider.project ?? + process.env.GOOGLE_CLOUD_PROJECT ?? + process.env.GCP_PROJECT ?? + process.env.GCLOUD_PROJECT + const location = + evt.provider.options.aisdk.provider.location ?? + process.env.GOOGLE_CLOUD_LOCATION ?? + process.env.VERTEX_LOCATION ?? + "global" if (project) evt.provider.options.aisdk.provider.project = project evt.provider.options.aisdk.provider.location = location }), diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 44c904aec..10bbb62da 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -10,7 +10,7 @@ export const OpencodePlugin = PluginV2.define({ "provider.update": Effect.fn(function* (evt) { if (evt.provider.id !== ProviderV2.ID.opencode) return hasKey = Boolean( - process.env.OPENCODE_API_KEY || + process.env.OPENCODE_API_KEY || evt.provider.env.some((item) => process.env[item]) || evt.provider.options.aisdk.provider.apiKey || (evt.provider.enabled && evt.provider.enabled.via === "auth"), diff --git a/packages/core/src/plugin/provider/sap-ai-core.ts b/packages/core/src/plugin/provider/sap-ai-core.ts index 619f01eb3..7c57b785b 100644 --- a/packages/core/src/plugin/provider/sap-ai-core.ts +++ b/packages/core/src/plugin/provider/sap-ai-core.ts @@ -29,7 +29,11 @@ export const SapAICorePlugin = PluginV2.define({ const match = Object.keys(mod).find((name) => name.startsWith("create")) if (!match) throw new Error(`Package ${evt.package} has no provider factory export`) - evt.sdk = mod[match](serviceKey ? { deploymentId: process.env.AICORE_DEPLOYMENT_ID, resourceGroup: process.env.AICORE_RESOURCE_GROUP } : {}) + evt.sdk = mod[match]( + serviceKey + ? { deploymentId: process.env.AICORE_DEPLOYMENT_ID, resourceGroup: process.env.AICORE_RESOURCE_GROUP } + : {}, + ) }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return diff --git a/packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts b/packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts index e7e53cb8d..c70ada08d 100644 --- a/packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts +++ b/packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts @@ -11,8 +11,9 @@ function bedrockBaseURL(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") { function bedrockFetch(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") { const language = (sdk as { languageModel: (id: string) => unknown }).languageModel(modelID) - return (language as { config: { fetch: (input: Parameters[0], init?: RequestInit) => Promise } }).config - .fetch + return ( + language as { config: { fetch: (input: Parameters[0], init?: RequestInit) => Promise } } + ).config.fetch } describe("AmazonBedrockPlugin", () => { diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index edd4fe119..85486480a 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -507,7 +507,11 @@ export type Patch = Types.DeepMutable["dat const db = (fn: (d: Parameters[0] extends (trx: infer D) => any ? D : never) => T) => Effect.sync(() => Database.use(fn)) -export const layer: Layer.Layer = Layer.effect( +export const layer: Layer.Layer< + Service, + never, + Bus.Service | Storage.Service | SyncEvent.Service | RuntimeFlags.Service +> = Layer.effect( Service, Effect.gen(function* () { const bus = yield* Bus.Service @@ -571,7 +575,9 @@ export const layer: Layer.Layer()("@opencode/Snapshot") {} -export const layer: Layer.Layer< - Service, - never, - AppFileSystem.Service | AppProcess.Service | Config.Service -> = Layer.effect( - Service, - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - const appProcess = yield* AppProcess.Service - const config = yield* Config.Service - const locks = new Map() - - const lock = (key: string) => { - const hit = locks.get(key) - if (hit) return hit - - const next = Semaphore.makeUnsafe(1) - locks.set(key, next) - return next - } - - const state = yield* InstanceState.make( - Effect.fn("Snapshot.state")(function* (ctx) { - const state = { - directory: ctx.directory, - worktree: ctx.worktree, - gitdir: path.join(Global.Path.data, "snapshot", ctx.project.id, Hash.fast(ctx.worktree)), - vcs: ctx.project.vcs, - } - - const args = (cmd: string[]) => ["--git-dir", state.gitdir, "--work-tree", state.worktree, ...cmd] - - const enc = new TextEncoder() - const feed = (list: string[]) => Stream.make(enc.encode(list.join("\0") + "\0")) - - const gitWithStdin = Effect.fnUntraced( - function* ( - cmd: string[], - opts: { cwd?: string; env?: Record; stdin: ChildProcess.CommandInput }, - ) { - // stdin-feed calls still need raw spawn — AppProcess.run does not yet - // expose a stdin Stream API. Tracked as future AppProcess helper. - const proc = ChildProcess.make("git", cmd, { - cwd: opts.cwd, - env: opts.env, - extendEnv: true, - stdin: opts.stdin, - }) - const handle = yield* appProcess.spawn(proc) - const [text, stderr] = yield* Effect.all( - [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], - { concurrency: 2 }, - ) - const code = yield* handle.exitCode - return { code, text, stderr } satisfies GitResult - }, - Effect.scoped, - Effect.catch((err) => - Effect.succeed({ - code: ChildProcessSpawner.ExitCode(1), - text: "", - stderr: err instanceof Error ? err.message : String(err), - }), - ), - ) - - const git = Effect.fnUntraced( - function* (cmd: string[], opts?: { cwd?: string; env?: Record }) { - const result = yield* appProcess.run( - ChildProcess.make("git", cmd, { - cwd: opts?.cwd, - env: opts?.env, +export const layer: Layer.Layer = + Layer.effect( + Service, + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const appProcess = yield* AppProcess.Service + const config = yield* Config.Service + const locks = new Map() + + const lock = (key: string) => { + const hit = locks.get(key) + if (hit) return hit + + const next = Semaphore.makeUnsafe(1) + locks.set(key, next) + return next + } + + const state = yield* InstanceState.make( + Effect.fn("Snapshot.state")(function* (ctx) { + const state = { + directory: ctx.directory, + worktree: ctx.worktree, + gitdir: path.join(Global.Path.data, "snapshot", ctx.project.id, Hash.fast(ctx.worktree)), + vcs: ctx.project.vcs, + } + + const args = (cmd: string[]) => ["--git-dir", state.gitdir, "--work-tree", state.worktree, ...cmd] + + const enc = new TextEncoder() + const feed = (list: string[]) => Stream.make(enc.encode(list.join("\0") + "\0")) + + const gitWithStdin = Effect.fnUntraced( + function* ( + cmd: string[], + opts: { cwd?: string; env?: Record; stdin: ChildProcess.CommandInput }, + ) { + // stdin-feed calls still need raw spawn — AppProcess.run does not yet + // expose a stdin Stream API. Tracked as future AppProcess helper. + const proc = ChildProcess.make("git", cmd, { + cwd: opts.cwd, + env: opts.env, extendEnv: true, - }), - ) - return { - code: ChildProcessSpawner.ExitCode(result.exitCode), - text: result.stdout.toString("utf8"), - stderr: result.stderr.toString("utf8"), - } satisfies GitResult - }, - Effect.catch((err) => - Effect.succeed({ - code: ChildProcessSpawner.ExitCode(1), - text: "", - stderr: err instanceof Error ? err.message : String(err), - }), - ), - ) - - const ignore = Effect.fnUntraced(function* (files: string[]) { - if (!files.length) return new Set() - const check = yield* gitWithStdin( - [ - ...quote, - "--git-dir", - path.join(state.worktree, ".git"), - "--work-tree", - state.worktree, - "check-ignore", - "--no-index", - "--stdin", - "-z", - ], - { - cwd: state.directory, - stdin: feed(files), - }, - ) - if (check.code !== 0 && check.code !== 1) return new Set() - return new Set(check.text.split("\0").filter(Boolean)) - }) - - const drop = Effect.fnUntraced(function* (files: string[]) { - if (!files.length) return - yield* gitWithStdin( - [ - ...cfg, - ...args(["rm", "--cached", "-f", "--ignore-unmatch", "--pathspec-from-file=-", "--pathspec-file-nul"]), - ], - { - cwd: state.directory, - stdin: feed(files), + stdin: opts.stdin, + }) + const handle = yield* appProcess.spawn(proc) + const [text, stderr] = yield* Effect.all( + [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], + { concurrency: 2 }, + ) + const code = yield* handle.exitCode + return { code, text, stderr } satisfies GitResult }, + Effect.scoped, + Effect.catch((err) => + Effect.succeed({ + code: ChildProcessSpawner.ExitCode(1), + text: "", + stderr: err instanceof Error ? err.message : String(err), + }), + ), ) - }) - - const stage = Effect.fnUntraced(function* (files: string[]) { - if (!files.length) return - const result = yield* gitWithStdin( - [...cfg, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])], - { - cwd: state.directory, - stdin: feed(files), + + const git = Effect.fnUntraced( + function* (cmd: string[], opts?: { cwd?: string; env?: Record }) { + const result = yield* appProcess.run( + ChildProcess.make("git", cmd, { + cwd: opts?.cwd, + env: opts?.env, + extendEnv: true, + }), + ) + return { + code: ChildProcessSpawner.ExitCode(result.exitCode), + text: result.stdout.toString("utf8"), + stderr: result.stderr.toString("utf8"), + } satisfies GitResult }, + Effect.catch((err) => + Effect.succeed({ + code: ChildProcessSpawner.ExitCode(1), + text: "", + stderr: err instanceof Error ? err.message : String(err), + }), + ), ) - if (result.code === 0) return - log.warn("failed to add snapshot files", { - exitCode: result.code, - stderr: result.stderr, - }) - }) - const exists = (file: string) => fs.exists(file).pipe(Effect.orDie) - const read = (file: string) => fs.readFileString(file).pipe(Effect.catch(() => Effect.succeed(""))) - const remove = (file: string) => fs.remove(file).pipe(Effect.catch(() => Effect.void)) - const locked = (fx: Effect.Effect) => lock(state.gitdir).withPermits(1)(fx) - - const enabled = Effect.fnUntraced(function* () { - if (state.vcs !== "git") return false - return (yield* config.get()).snapshot !== false - }) - - const excludes = Effect.fnUntraced(function* () { - const result = yield* git(["rev-parse", "--path-format=absolute", "--git-path", "info/exclude"], { - cwd: state.worktree, + const ignore = Effect.fnUntraced(function* (files: string[]) { + if (!files.length) return new Set() + const check = yield* gitWithStdin( + [ + ...quote, + "--git-dir", + path.join(state.worktree, ".git"), + "--work-tree", + state.worktree, + "check-ignore", + "--no-index", + "--stdin", + "-z", + ], + { + cwd: state.directory, + stdin: feed(files), + }, + ) + if (check.code !== 0 && check.code !== 1) return new Set() + return new Set(check.text.split("\0").filter(Boolean)) }) - const file = result.text.trim() - if (!file) return - if (!(yield* exists(file))) return - return file - }) - - const sync = Effect.fnUntraced(function* (list: string[] = []) { - const file = yield* excludes() - const target = path.join(state.gitdir, "info", "exclude") - const text = [ - file ? (yield* read(file)).trimEnd() : "", - ...list.map((item) => `/${item.replaceAll("\\", "/")}`), - ] - .filter(Boolean) - .join("\n") - yield* fs.ensureDir(path.join(state.gitdir, "info")).pipe(Effect.orDie) - yield* fs.writeFileString(target, text ? `${text}\n` : "").pipe(Effect.orDie) - }) - - const add = Effect.fnUntraced(function* () { - yield* sync() - const [diff, other] = yield* Effect.all( - [ - git([...quote, ...args(["diff-files", "--name-only", "-z", "--", "."])], { + + const drop = Effect.fnUntraced(function* (files: string[]) { + if (!files.length) return + yield* gitWithStdin( + [ + ...cfg, + ...args(["rm", "--cached", "-f", "--ignore-unmatch", "--pathspec-from-file=-", "--pathspec-file-nul"]), + ], + { cwd: state.directory, - }), - git([...quote, ...args(["ls-files", "--others", "--exclude-standard", "-z", "--", "."])], { + stdin: feed(files), + }, + ) + }) + + const stage = Effect.fnUntraced(function* (files: string[]) { + if (!files.length) return + const result = yield* gitWithStdin( + [...cfg, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])], + { cwd: state.directory, - }), - ], - { concurrency: 2 }, - ) - if (diff.code !== 0 || other.code !== 0) { - log.warn("failed to list snapshot files", { - diffCode: diff.code, - diffStderr: diff.stderr, - otherCode: other.code, - otherStderr: other.stderr, + stdin: feed(files), + }, + ) + if (result.code === 0) return + log.warn("failed to add snapshot files", { + exitCode: result.code, + stderr: result.stderr, }) - return - } + }) - const tracked = diff.text.split("\0").filter(Boolean) - const untracked = other.text.split("\0").filter(Boolean) - const all = Array.from(new Set([...tracked, ...untracked])) - if (!all.length) return + const exists = (file: string) => fs.exists(file).pipe(Effect.orDie) + const read = (file: string) => fs.readFileString(file).pipe(Effect.catch(() => Effect.succeed(""))) + const remove = (file: string) => fs.remove(file).pipe(Effect.catch(() => Effect.void)) + const locked = (fx: Effect.Effect) => lock(state.gitdir).withPermits(1)(fx) - // Resolve source-repo ignore rules against the exact candidate set. - // --no-index keeps this pattern-based even when a path is already tracked. - const ignored = yield* ignore(all) + const enabled = Effect.fnUntraced(function* () { + if (state.vcs !== "git") return false + return (yield* config.get()).snapshot !== false + }) - // Remove newly-ignored files from snapshot index to prevent re-adding - if (ignored.size > 0) { - const ignoredFiles = Array.from(ignored) - log.info("removing gitignored files from snapshot", { count: ignoredFiles.length }) - yield* drop(ignoredFiles) - } + const excludes = Effect.fnUntraced(function* () { + const result = yield* git(["rev-parse", "--path-format=absolute", "--git-path", "info/exclude"], { + cwd: state.worktree, + }) + const file = result.text.trim() + if (!file) return + if (!(yield* exists(file))) return + return file + }) - const allow = all.filter((item) => !ignored.has(item)) - if (!allow.length) return - - const large = new Set( - (yield* Effect.all( - allow.map((item) => - fs - .stat(path.join(state.directory, item)) - .pipe(Effect.catch(() => Effect.void)) - .pipe( - Effect.map((stat) => { - if (!stat || stat.type !== "File") return - const size = typeof stat.size === "bigint" ? Number(stat.size) : stat.size - return size > limit ? item : undefined - }), - ), - ), - { concurrency: 8 }, - )).filter((item): item is string => Boolean(item)), - ) - const block = new Set(untracked.filter((item) => large.has(item))) - yield* sync(Array.from(block)) - // Stage only the allowed candidate paths so snapshot updates stay scoped. - yield* stage(allow.filter((item) => !block.has(item))) - }) - - const cleanup = Effect.fnUntraced(function* () { - return yield* locked( - Effect.gen(function* () { - if (!(yield* enabled())) return - if (!(yield* exists(state.gitdir))) return - const result = yield* git(args(["gc", `--prune=${prune}`]), { cwd: state.directory }) - if (result.code !== 0) { - log.warn("cleanup failed", { - exitCode: result.code, - stderr: result.stderr, - }) - return - } - log.info("cleanup", { prune }) - }), - ) - }) - - const track = Effect.fnUntraced(function* () { - return yield* locked( - Effect.gen(function* () { - if (!(yield* enabled())) return - const existed = yield* exists(state.gitdir) - yield* fs.ensureDir(state.gitdir).pipe(Effect.orDie) - if (!existed) { - yield* git(["init"], { - env: { GIT_DIR: state.gitdir, GIT_WORK_TREE: state.worktree }, - }) - yield* git(["--git-dir", state.gitdir, "config", "core.autocrlf", "false"]) - yield* git(["--git-dir", state.gitdir, "config", "core.longpaths", "true"]) - yield* git(["--git-dir", state.gitdir, "config", "core.symlinks", "true"]) - yield* git(["--git-dir", state.gitdir, "config", "core.fsmonitor", "false"]) - log.info("initialized") - } - yield* add() - const result = yield* git(args(["write-tree"]), { cwd: state.directory }) - const hash = result.text.trim() - log.info("tracking", { hash, cwd: state.directory, git: state.gitdir }) - return hash - }), - ) - }) - - const patch = Effect.fnUntraced(function* (hash: string) { - return yield* locked( - Effect.gen(function* () { - yield* add() - const result = yield* git( - [...quote, ...args(["diff", "--cached", "--no-ext-diff", "--name-only", hash, "--", "."])], - { - cwd: state.directory, - }, - ) - if (result.code !== 0) { - log.warn("failed to get diff", { hash, exitCode: result.code }) - return { hash, files: [] } - } - const files = result.text - .trim() - .split("\n") - .map((x) => x.trim()) - .filter(Boolean) - - // Hide ignored-file removals from the user-facing patch output. - const ignored = yield* ignore(files) + const sync = Effect.fnUntraced(function* (list: string[] = []) { + const file = yield* excludes() + const target = path.join(state.gitdir, "info", "exclude") + const text = [ + file ? (yield* read(file)).trimEnd() : "", + ...list.map((item) => `/${item.replaceAll("\\", "/")}`), + ] + .filter(Boolean) + .join("\n") + yield* fs.ensureDir(path.join(state.gitdir, "info")).pipe(Effect.orDie) + yield* fs.writeFileString(target, text ? `${text}\n` : "").pipe(Effect.orDie) + }) - return { - hash, - files: files - .filter((item) => !ignored.has(item)) - .map((x) => path.join(state.worktree, x).replaceAll("\\", "/")), - } - }), - ) - }) - - const restore = Effect.fnUntraced(function* (snapshot: string) { - return yield* locked( - Effect.gen(function* () { - log.info("restore", { commit: snapshot }) - const result = yield* git([...core, ...args(["read-tree", snapshot])], { cwd: state.worktree }) - if (result.code === 0) { - const checkout = yield* git([...core, ...args(["checkout-index", "-a", "-f"])], { - cwd: state.worktree, - }) - if (checkout.code === 0) return - log.error("failed to restore snapshot", { - snapshot, - exitCode: checkout.code, - stderr: checkout.stderr, - }) - return - } - log.error("failed to restore snapshot", { - snapshot, - exitCode: result.code, - stderr: result.stderr, + const add = Effect.fnUntraced(function* () { + yield* sync() + const [diff, other] = yield* Effect.all( + [ + git([...quote, ...args(["diff-files", "--name-only", "-z", "--", "."])], { + cwd: state.directory, + }), + git([...quote, ...args(["ls-files", "--others", "--exclude-standard", "-z", "--", "."])], { + cwd: state.directory, + }), + ], + { concurrency: 2 }, + ) + if (diff.code !== 0 || other.code !== 0) { + log.warn("failed to list snapshot files", { + diffCode: diff.code, + diffStderr: diff.stderr, + otherCode: other.code, + otherStderr: other.stderr, }) - }), - ) - }) - - const revert = Effect.fnUntraced(function* (patches: Patch[]) { - return yield* locked( - Effect.gen(function* () { - const ops: { hash: string; file: string; rel: string }[] = [] - const seen = new Set() - for (const item of patches) { - for (const file of item.files) { - if (seen.has(file)) continue - seen.add(file) - ops.push({ - hash: item.hash, - file, - rel: path.relative(state.worktree, file).replaceAll("\\", "/"), - }) - } - } + return + } + + const tracked = diff.text.split("\0").filter(Boolean) + const untracked = other.text.split("\0").filter(Boolean) + const all = Array.from(new Set([...tracked, ...untracked])) + if (!all.length) return + + // Resolve source-repo ignore rules against the exact candidate set. + // --no-index keeps this pattern-based even when a path is already tracked. + const ignored = yield* ignore(all) + + // Remove newly-ignored files from snapshot index to prevent re-adding + if (ignored.size > 0) { + const ignoredFiles = Array.from(ignored) + log.info("removing gitignored files from snapshot", { count: ignoredFiles.length }) + yield* drop(ignoredFiles) + } + + const allow = all.filter((item) => !ignored.has(item)) + if (!allow.length) return + + const large = new Set( + (yield* Effect.all( + allow.map((item) => + fs + .stat(path.join(state.directory, item)) + .pipe(Effect.catch(() => Effect.void)) + .pipe( + Effect.map((stat) => { + if (!stat || stat.type !== "File") return + const size = typeof stat.size === "bigint" ? Number(stat.size) : stat.size + return size > limit ? item : undefined + }), + ), + ), + { concurrency: 8 }, + )).filter((item): item is string => Boolean(item)), + ) + const block = new Set(untracked.filter((item) => large.has(item))) + yield* sync(Array.from(block)) + // Stage only the allowed candidate paths so snapshot updates stay scoped. + yield* stage(allow.filter((item) => !block.has(item))) + }) - const single = Effect.fnUntraced(function* (op: (typeof ops)[number]) { - log.info("reverting", { file: op.file, hash: op.hash }) - const result = yield* git([...core, ...args(["checkout", op.hash, "--", op.file])], { - cwd: state.worktree, - }) - if (result.code === 0) return - const tree = yield* git([...core, ...args(["ls-tree", op.hash, "--", op.rel])], { - cwd: state.worktree, - }) - if (tree.code === 0 && tree.text.trim()) { - log.info("file existed in snapshot but checkout failed, keeping", { file: op.file, hash: op.hash }) + const cleanup = Effect.fnUntraced(function* () { + return yield* locked( + Effect.gen(function* () { + if (!(yield* enabled())) return + if (!(yield* exists(state.gitdir))) return + const result = yield* git(args(["gc", `--prune=${prune}`]), { cwd: state.directory }) + if (result.code !== 0) { + log.warn("cleanup failed", { + exitCode: result.code, + stderr: result.stderr, + }) return } - log.info("file did not exist in snapshot, deleting", { file: op.file, hash: op.hash }) - yield* remove(op.file) - }) - - const clash = (a: string, b: string) => a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`) - - for (let i = 0; i < ops.length; ) { - const first = ops[i]! - const run = [first] - let j = i + 1 - // Only batch adjacent files when their paths cannot affect each other. - while (j < ops.length && run.length < 100) { - const next = ops[j]! - if (next.hash !== first.hash) break - if (run.some((item) => clash(item.rel, next.rel))) break - run.push(next) - j += 1 - } + log.info("cleanup", { prune }) + }), + ) + }) - if (run.length === 1) { - yield* single(first) - i = j - continue + const track = Effect.fnUntraced(function* () { + return yield* locked( + Effect.gen(function* () { + if (!(yield* enabled())) return + const existed = yield* exists(state.gitdir) + yield* fs.ensureDir(state.gitdir).pipe(Effect.orDie) + if (!existed) { + yield* git(["init"], { + env: { GIT_DIR: state.gitdir, GIT_WORK_TREE: state.worktree }, + }) + yield* git(["--git-dir", state.gitdir, "config", "core.autocrlf", "false"]) + yield* git(["--git-dir", state.gitdir, "config", "core.longpaths", "true"]) + yield* git(["--git-dir", state.gitdir, "config", "core.symlinks", "true"]) + yield* git(["--git-dir", state.gitdir, "config", "core.fsmonitor", "false"]) + log.info("initialized") } + yield* add() + const result = yield* git(args(["write-tree"]), { cwd: state.directory }) + const hash = result.text.trim() + log.info("tracking", { hash, cwd: state.directory, git: state.gitdir }) + return hash + }), + ) + }) - const tree = yield* git( - [...core, ...args(["ls-tree", "--name-only", first.hash, "--", ...run.map((item) => item.rel)])], + const patch = Effect.fnUntraced(function* (hash: string) { + return yield* locked( + Effect.gen(function* () { + yield* add() + const result = yield* git( + [...quote, ...args(["diff", "--cached", "--no-ext-diff", "--name-only", hash, "--", "."])], { - cwd: state.worktree, + cwd: state.directory, }, ) + if (result.code !== 0) { + log.warn("failed to get diff", { hash, exitCode: result.code }) + return { hash, files: [] } + } + const files = result.text + .trim() + .split("\n") + .map((x) => x.trim()) + .filter(Boolean) + + // Hide ignored-file removals from the user-facing patch output. + const ignored = yield* ignore(files) + + return { + hash, + files: files + .filter((item) => !ignored.has(item)) + .map((x) => path.join(state.worktree, x).replaceAll("\\", "/")), + } + }), + ) + }) - if (tree.code !== 0) { - log.info("batched ls-tree failed, falling back to single-file revert", { - hash: first.hash, - files: run.length, + const restore = Effect.fnUntraced(function* (snapshot: string) { + return yield* locked( + Effect.gen(function* () { + log.info("restore", { commit: snapshot }) + const result = yield* git([...core, ...args(["read-tree", snapshot])], { cwd: state.worktree }) + if (result.code === 0) { + const checkout = yield* git([...core, ...args(["checkout-index", "-a", "-f"])], { + cwd: state.worktree, }) - for (const op of run) { - yield* single(op) + if (checkout.code === 0) return + log.error("failed to restore snapshot", { + snapshot, + exitCode: checkout.code, + stderr: checkout.stderr, + }) + return + } + log.error("failed to restore snapshot", { + snapshot, + exitCode: result.code, + stderr: result.stderr, + }) + }), + ) + }) + + const revert = Effect.fnUntraced(function* (patches: Patch[]) { + return yield* locked( + Effect.gen(function* () { + const ops: { hash: string; file: string; rel: string }[] = [] + const seen = new Set() + for (const item of patches) { + for (const file of item.files) { + if (seen.has(file)) continue + seen.add(file) + ops.push({ + hash: item.hash, + file, + rel: path.relative(state.worktree, file).replaceAll("\\", "/"), + }) } - i = j - continue } - const have = new Set( - tree.text - .trim() - .split("\n") - .map((item) => item.trim()) - .filter(Boolean), - ) - const list = run.filter((item) => have.has(item.rel)) - if (list.length) { - log.info("reverting", { hash: first.hash, files: list.length }) - const result = yield* git( - [...core, ...args(["checkout", first.hash, "--", ...list.map((item) => item.file)])], + const single = Effect.fnUntraced(function* (op: (typeof ops)[number]) { + log.info("reverting", { file: op.file, hash: op.hash }) + const result = yield* git([...core, ...args(["checkout", op.hash, "--", op.file])], { + cwd: state.worktree, + }) + if (result.code === 0) return + const tree = yield* git([...core, ...args(["ls-tree", op.hash, "--", op.rel])], { + cwd: state.worktree, + }) + if (tree.code === 0 && tree.text.trim()) { + log.info("file existed in snapshot but checkout failed, keeping", { file: op.file, hash: op.hash }) + return + } + log.info("file did not exist in snapshot, deleting", { file: op.file, hash: op.hash }) + yield* remove(op.file) + }) + + const clash = (a: string, b: string) => a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`) + + for (let i = 0; i < ops.length; ) { + const first = ops[i]! + const run = [first] + let j = i + 1 + // Only batch adjacent files when their paths cannot affect each other. + while (j < ops.length && run.length < 100) { + const next = ops[j]! + if (next.hash !== first.hash) break + if (run.some((item) => clash(item.rel, next.rel))) break + run.push(next) + j += 1 + } + + if (run.length === 1) { + yield* single(first) + i = j + continue + } + + const tree = yield* git( + [...core, ...args(["ls-tree", "--name-only", first.hash, "--", ...run.map((item) => item.rel)])], { cwd: state.worktree, }, ) - if (result.code !== 0) { - log.info("batched checkout failed, falling back to single-file revert", { + + if (tree.code !== 0) { + log.info("batched ls-tree failed, falling back to single-file revert", { hash: first.hash, - files: list.length, + files: run.length, }) for (const op of run) { yield* single(op) @@ -499,300 +468,330 @@ export const layer: Layer.Layer< i = j continue } - } - for (const op of run) { - if (have.has(op.rel)) continue - log.info("file did not exist in snapshot, deleting", { file: op.file, hash: op.hash }) - yield* remove(op.file) + const have = new Set( + tree.text + .trim() + .split("\n") + .map((item) => item.trim()) + .filter(Boolean), + ) + const list = run.filter((item) => have.has(item.rel)) + if (list.length) { + log.info("reverting", { hash: first.hash, files: list.length }) + const result = yield* git( + [...core, ...args(["checkout", first.hash, "--", ...list.map((item) => item.file)])], + { + cwd: state.worktree, + }, + ) + if (result.code !== 0) { + log.info("batched checkout failed, falling back to single-file revert", { + hash: first.hash, + files: list.length, + }) + for (const op of run) { + yield* single(op) + } + i = j + continue + } + } + + for (const op of run) { + if (have.has(op.rel)) continue + log.info("file did not exist in snapshot, deleting", { file: op.file, hash: op.hash }) + yield* remove(op.file) + } + + i = j } + }), + ) + }) - i = j - } - }), - ) - }) - - const diff = Effect.fnUntraced(function* (hash: string) { - return yield* locked( - Effect.gen(function* () { - yield* add() - const result = yield* git([...quote, ...args(["diff", "--cached", "--no-ext-diff", hash, "--", "."])], { - cwd: state.worktree, - }) - if (result.code !== 0) { - log.warn("failed to get diff", { - hash, - exitCode: result.code, - stderr: result.stderr, + const diff = Effect.fnUntraced(function* (hash: string) { + return yield* locked( + Effect.gen(function* () { + yield* add() + const result = yield* git([...quote, ...args(["diff", "--cached", "--no-ext-diff", hash, "--", "."])], { + cwd: state.worktree, }) - return "" - } - return result.text.trim() - }), - ) - }) - - 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 (result.code !== 0) { + log.warn("failed to get diff", { + hash, + exitCode: result.code, + stderr: result.stderr, + }) + return "" } - if (row.status === "deleted") { - return [ - yield* git([...cfg, ...args(["show", `${from}:${row.file}`])]).pipe( - Effect.map((item) => item.text), - ), - "", - ] + return result.text.trim() + }), + ) + }) + + 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 } - 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] - } + 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 [ - { file: row.file, side: "before", ref: `${from}:${row.file}` } satisfies Ref, - { file: row.file, side: "after", ref: `${to}:${row.file}` } satisfies Ref, + "", + yield* git([...cfg, ...args(["show", `${to}:${row.file}`])]).pipe( + Effect.map((item) => item.text), + ), ] - }) - if (!refs.length) return new Map() - - // cat-file --batch is a stdin-feed call — kept on raw spawn - // until AppProcess.run exposes a stdin Stream API. - 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* appProcess.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 + 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 map = new Map() - const dec = new TextDecoder() - let i = 0 - 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 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() + + // cat-file --batch is a stdin-feed call — kept on raw spawn + // until AppProcess.run exposes a stdin Stream API. + 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* appProcess.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 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 fail = (msg: string, extra?: Record) => { + log.info(msg, { ...extra, refs: refs.length }) + return undefined } - 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 map = new Map() + const dec = new TextDecoder() + let i = 0 + 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 } - const size = Number(match[1]) - if (!Number.isInteger(size) || size < 0 || i + size >= out.length || out[i + size] !== 10) { + if (i !== out.length) { return fail( - "git cat-file --batch returned truncated content during snapshot diff, falling back to per-file git show", - { head }, + "git cat-file --batch returned trailing data during snapshot diff, falling back to per-file git show", ) } - 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), + ), + ) - return map - }, - Effect.scoped, - Effect.catch(() => - Effect.succeed | undefined>(undefined), - ), - ) + const result: FileDiff[] = [] + const status = new Map() - const result: FileDiff[] = [] - const status = new Map() + const statuses = yield* git( + [...quote, ...args(["diff", "--no-ext-diff", "--name-status", "--no-renames", from, to, "--", "."])], + { cwd: state.directory }, + ) - const statuses = yield* git( - [...quote, ...args(["diff", "--no-ext-diff", "--name-status", "--no-renames", from, to, "--", "."])], - { cwd: state.directory }, - ) + for (const line of statuses.text.trim().split("\n")) { + if (!line) continue + const [code, file] = line.split("\t") + if (!code || !file) continue + status.set(file, code.startsWith("A") ? "added" : code.startsWith("D") ? "deleted" : "modified") + } - for (const line of statuses.text.trim().split("\n")) { - if (!line) continue - const [code, file] = line.split("\t") - if (!code || !file) continue - status.set(file, code.startsWith("A") ? "added" : code.startsWith("D") ? "deleted" : "modified") - } + const numstat = yield* git( + [...quote, ...args(["diff", "--no-ext-diff", "--no-renames", "--numstat", from, to, "--", "."])], + { + cwd: state.directory, + }, + ) - const numstat = yield* git( - [...quote, ...args(["diff", "--no-ext-diff", "--no-renames", "--numstat", from, to, "--", "."])], - { - cwd: state.directory, - }, - ) + 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 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, - ] - }) + // Hide ignored-file removals from the user-facing diff output. + const ignored = yield* ignore(rows.map((r) => r.file)) + if (ignored.size > 0) { + const filtered = rows.filter((r) => !ignored.has(r.file)) + rows.length = 0 + rows.push(...filtered) + } - // Hide ignored-file removals from the user-facing diff output. - const ignored = yield* ignore(rows.map((r) => r.file)) - if (ignored.size > 0) { - const filtered = rows.filter((r) => !ignored.has(r.file)) - rows.length = 0 - rows.push(...filtered) - } - - const step = 100 - const patch = (file: string, before: string, after: string) => - formatPatch(structuredPatch(file, file, before, after, "", "", { context: Number.MAX_SAFE_INTEGER })) - - 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, - patch: row.binary ? "" : patch(row.file, before, after), - additions: row.additions, - deletions: row.deletions, - status: row.status, - }) + const step = 100 + const patch = (file: string, before: string, after: string) => + formatPatch(structuredPatch(file, file, before, after, "", "", { context: Number.MAX_SAFE_INTEGER })) + + 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, + patch: row.binary ? "" : patch(row.file, before, after), + additions: row.additions, + deletions: row.deletions, + status: row.status, + }) + } } - } - return result + return result + }), + ) + }) + + yield* cleanup().pipe( + Effect.catchCause((cause) => { + log.error("cleanup loop failed", { cause: Cause.pretty(cause) }) + return Effect.void }), + Effect.repeat(Schedule.spaced(Duration.hours(1))), + Effect.delay(Duration.minutes(1)), + Effect.forkScoped, ) - }) - - yield* cleanup().pipe( - Effect.catchCause((cause) => { - log.error("cleanup loop failed", { cause: Cause.pretty(cause) }) - return Effect.void - }), - Effect.repeat(Schedule.spaced(Duration.hours(1))), - Effect.delay(Duration.minutes(1)), - Effect.forkScoped, - ) - - return { cleanup, track, patch, restore, revert, diff, diffFull } - }), - ) - - return Service.of({ - init: Effect.fn("Snapshot.init")(function* () { - yield* InstanceState.get(state) - }), - cleanup: Effect.fn("Snapshot.cleanup")(function* () { - return yield* InstanceState.useEffect(state, (s) => s.cleanup()) - }), - track: Effect.fn("Snapshot.track")(function* () { - return yield* InstanceState.useEffect(state, (s) => s.track()) - }), - patch: Effect.fn("Snapshot.patch")(function* (hash: string) { - return yield* InstanceState.useEffect(state, (s) => s.patch(hash)) - }), - restore: Effect.fn("Snapshot.restore")(function* (snapshot: string) { - return yield* InstanceState.useEffect(state, (s) => s.restore(snapshot)) - }), - revert: Effect.fn("Snapshot.revert")(function* (patches: Patch[]) { - return yield* InstanceState.useEffect(state, (s) => s.revert(patches)) - }), - diff: Effect.fn("Snapshot.diff")(function* (hash: string) { - return yield* InstanceState.useEffect(state, (s) => s.diff(hash)) - }), - diffFull: Effect.fn("Snapshot.diffFull")(function* (from: string, to: string) { - return yield* InstanceState.useEffect(state, (s) => s.diffFull(from, to)) - }), - }) - }), -) + + return { cleanup, track, patch, restore, revert, diff, diffFull } + }), + ) + + return Service.of({ + init: Effect.fn("Snapshot.init")(function* () { + yield* InstanceState.get(state) + }), + cleanup: Effect.fn("Snapshot.cleanup")(function* () { + return yield* InstanceState.useEffect(state, (s) => s.cleanup()) + }), + track: Effect.fn("Snapshot.track")(function* () { + return yield* InstanceState.useEffect(state, (s) => s.track()) + }), + patch: Effect.fn("Snapshot.patch")(function* (hash: string) { + return yield* InstanceState.useEffect(state, (s) => s.patch(hash)) + }), + restore: Effect.fn("Snapshot.restore")(function* (snapshot: string) { + return yield* InstanceState.useEffect(state, (s) => s.restore(snapshot)) + }), + revert: Effect.fn("Snapshot.revert")(function* (patches: Patch[]) { + return yield* InstanceState.useEffect(state, (s) => s.revert(patches)) + }), + diff: Effect.fn("Snapshot.diff")(function* (hash: string) { + return yield* InstanceState.useEffect(state, (s) => s.diff(hash)) + }), + diffFull: Effect.fn("Snapshot.diffFull")(function* (from: string, to: string) { + return yield* InstanceState.useEffect(state, (s) => s.diffFull(from, to)) + }), + }) + }), + ) export const defaultLayer = layer.pipe( Layer.provide(AppProcess.defaultLayer), diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index e6e0c4638..37b938574 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -198,6 +198,9 @@ import type { TuiShowToastResponses, TuiSubmitPromptResponses, V2ModelListResponses, + V2ProviderGetErrors, + V2ProviderGetResponses, + V2ProviderListResponses, V2SessionCompactResponses, V2SessionContextResponses, V2SessionListErrors, @@ -4382,26 +4385,41 @@ export class Model extends HeyApiClient { * * Retrieve available v2 models ordered by release date. */ - public list( - parameters?: { - directory?: string - workspace?: string + public list(options?: Options) { + return (options?.client ?? this.client).get({ + url: "/api/model", + ...options, + }) + } +} + +export class Provider2 extends HeyApiClient { + /** + * List v2 providers + * + * Retrieve active v2 AI providers so clients can show provider availability and configuration. + */ + public list(options?: Options) { + return (options?.client ?? this.client).get({ + url: "/api/provider", + ...options, + }) + } + + /** + * Get v2 provider + * + * Retrieve a single v2 AI provider so clients can inspect its availability and endpoint settings. + */ + public get( + parameters: { + providerID: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/api/model", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "providerID" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/provider/{providerID}", ...options, ...params, }) @@ -4418,6 +4436,11 @@ export class V2 extends HeyApiClient { get model(): Model { return (this._model ??= new Model({ client: this.client })) } + + private _provider?: Provider2 + get provider(): Provider2 { + return (this._provider ??= new Provider2({ client: this.client })) + } } export class Control extends HeyApiClient { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 99bbfd5ec..014a5fbab 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3380,10 +3380,14 @@ export type SessionMessage = export type ModelV2Info = { id: string + apiID: string providerID: string family?: string name: string endpoint: + | { + type: "unknown" + } | { type: "openai/responses" url: string @@ -3404,6 +3408,11 @@ export type ModelV2Info = { type: "anthropic/messages" url: string } + | { + type: "aisdk" + package: string + url?: string + } capabilities: { tools: boolean input: Array @@ -3416,6 +3425,14 @@ export type ModelV2Info = { body: { [key: string]: unknown } + aisdk: { + provider: { + [key: string]: unknown + } + request: { + [key: string]: unknown + } + } variant?: string } variants: Array<{ @@ -3426,6 +3443,14 @@ export type ModelV2Info = { body: { [key: string]: unknown } + aisdk: { + provider: { + [key: string]: unknown + } + request: { + [key: string]: unknown + } + } }> time: { released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" @@ -3443,6 +3468,7 @@ export type ModelV2Info = { } }> status: "alpha" | "beta" | "deprecated" | "active" + enabled: boolean limit: { context: number input?: number @@ -3450,6 +3476,73 @@ export type ModelV2Info = { } } +export type ProviderV2Info = { + id: string + name: string + enabled: + | false + | { + via: "env" + name: string + } + | { + via: "auth" + service: string + } + | { + via: "custom" + data: { + [key: string]: unknown + } + } + env: Array + endpoint: + | { + type: "unknown" + } + | { + type: "openai/responses" + url: string + websocket?: boolean + } + | { + type: "openai/completions" + url: string + reasoning?: + | { + type: "reasoning_content" + } + | { + type: "reasoning_details" + } + } + | { + type: "anthropic/messages" + url: string + } + | { + type: "aisdk" + package: string + url?: string + } + options: { + headers: { + [key: string]: string + } + body: { + [key: string]: unknown + } + aisdk: { + provider: { + [key: string]: unknown + } + request: { + [key: string]: unknown + } + } + } +} + export type EventTuiToastShow1 = { id: string type: "tui.toast.show" @@ -6580,10 +6673,7 @@ export type V2SessionMessagesResponse2 = V2SessionMessagesResponses[keyof V2Sess export type V2ModelListData = { body?: never path?: never - query?: { - directory?: string - workspace?: string - } + query?: never url: "/api/model" } @@ -6596,6 +6686,49 @@ export type V2ModelListResponses = { export type V2ModelListResponse = V2ModelListResponses[keyof V2ModelListResponses] +export type V2ProviderListData = { + body?: never + path?: never + query?: never + url: "/api/provider" +} + +export type V2ProviderListResponses = { + /** + * Success + */ + 200: Array +} + +export type V2ProviderListResponse = V2ProviderListResponses[keyof V2ProviderListResponses] + +export type V2ProviderGetData = { + body?: never + path: { + providerID: string + } + query?: never + url: "/api/provider/{providerID}" +} + +export type V2ProviderGetErrors = { + /** + * NotFoundError + */ + 404: NotFoundError +} + +export type V2ProviderGetError = V2ProviderGetErrors[keyof V2ProviderGetErrors] + +export type V2ProviderGetResponses = { + /** + * ProviderV2.Info + */ + 200: ProviderV2Info +} + +export type V2ProviderGetResponse = V2ProviderGetResponses[keyof V2ProviderGetResponses] + export type TuiAppendPromptData = { body?: { text: string diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 97890a5dc..114db9cd7 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -7606,6 +7606,112 @@ ] } }, + "/api/model": { + "get": { + "tags": ["v2 models"], + "operationId": "v2.model.list", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ModelV2Info" + } + } + } + } + } + }, + "description": "Retrieve available v2 models ordered by release date.", + "summary": "List v2 models", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.model.list({\n ...\n})" + } + ] + } + }, + "/api/provider": { + "get": { + "tags": ["v2 providers"], + "operationId": "v2.provider.list", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProviderV2Info" + } + } + } + } + } + }, + "description": "Retrieve active v2 AI providers so clients can show provider availability and configuration.", + "summary": "List v2 providers", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.provider.list({\n ...\n})" + } + ] + } + }, + "/api/provider/{providerID}": { + "get": { + "tags": ["v2 providers"], + "operationId": "v2.provider.get", + "parameters": [ + { + "name": "providerID", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ProviderV2.Info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProviderV2Info" + } + } + } + }, + "404": { + "description": "NotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "description": "Retrieve a single v2 AI provider so clients can inspect its availability and endpoint settings.", + "summary": "Get v2 provider", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.provider.get({\n ...\n})" + } + ] + } + }, "/tui/append-prompt": { "post": { "tags": ["tui"], @@ -18991,6 +19097,531 @@ } ] }, + "ModelV2Info": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "apiID": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "family": { + "type": "string" + }, + "name": { + "type": "string" + }, + "endpoint": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["unknown"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["openai/responses"] + }, + "url": { + "type": "string" + }, + "websocket": { + "type": "boolean" + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["openai/completions"] + }, + "url": { + "type": "string" + }, + "reasoning": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["reasoning_content"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["reasoning_details"] + } + }, + "required": ["type"], + "additionalProperties": false + } + ] + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["anthropic/messages"] + }, + "url": { + "type": "string" + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["aisdk"] + }, + "package": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": ["type", "package"], + "additionalProperties": false + } + ] + }, + "capabilities": { + "type": "object", + "properties": { + "tools": { + "type": "boolean" + }, + "input": { + "type": "array", + "items": { + "type": "string" + } + }, + "output": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["tools", "input", "output"], + "additionalProperties": false + }, + "options": { + "type": "object", + "properties": { + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" + }, + "aisdk": { + "type": "object", + "properties": { + "provider": { + "type": "object" + }, + "request": { + "type": "object" + } + }, + "required": ["provider", "request"], + "additionalProperties": false + }, + "variant": { + "type": "string" + } + }, + "required": ["headers", "body", "aisdk"], + "additionalProperties": false + }, + "variants": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" + }, + "aisdk": { + "type": "object", + "properties": { + "provider": { + "type": "object" + }, + "request": { + "type": "object" + } + }, + "required": ["provider", "request"], + "additionalProperties": false + } + }, + "required": ["id", "headers", "body", "aisdk"], + "additionalProperties": false + } + }, + "time": { + "type": "object", + "properties": { + "released": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": ["NaN"] + }, + { + "type": "string", + "enum": ["Infinity"] + }, + { + "type": "string", + "enum": ["-Infinity"] + }, + { + "type": "string", + "enum": ["Infinity", "-Infinity", "NaN"] + } + ] + } + }, + "required": ["released"], + "additionalProperties": false + }, + "cost": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tier": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["context"] + }, + "size": { + "type": "integer" + } + }, + "required": ["type", "size"], + "additionalProperties": false + }, + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"], + "additionalProperties": false + } + }, + "required": ["input", "output", "cache"], + "additionalProperties": false + } + }, + "status": { + "type": "string", + "enum": ["alpha", "beta", "deprecated", "active"] + }, + "enabled": { + "type": "boolean" + }, + "limit": { + "type": "object", + "properties": { + "context": { + "type": "integer" + }, + "input": { + "type": "integer" + }, + "output": { + "type": "integer" + } + }, + "required": ["context", "output"], + "additionalProperties": false + } + }, + "required": [ + "id", + "apiID", + "providerID", + "name", + "endpoint", + "capabilities", + "options", + "variants", + "time", + "cost", + "status", + "enabled", + "limit" + ], + "additionalProperties": false + }, + "ProviderV2Info": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "enabled": { + "anyOf": [ + { + "type": "boolean", + "enum": [false] + }, + { + "type": "object", + "properties": { + "via": { + "type": "string", + "enum": ["env"] + }, + "name": { + "type": "string" + } + }, + "required": ["via", "name"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "via": { + "type": "string", + "enum": ["auth"] + }, + "service": { + "type": "string" + } + }, + "required": ["via", "service"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "via": { + "type": "string", + "enum": ["custom"] + }, + "data": { + "type": "object" + } + }, + "required": ["via", "data"], + "additionalProperties": false + } + ] + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "endpoint": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["unknown"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["openai/responses"] + }, + "url": { + "type": "string" + }, + "websocket": { + "type": "boolean" + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["openai/completions"] + }, + "url": { + "type": "string" + }, + "reasoning": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["reasoning_content"] + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["reasoning_details"] + } + }, + "required": ["type"], + "additionalProperties": false + } + ] + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["anthropic/messages"] + }, + "url": { + "type": "string" + } + }, + "required": ["type", "url"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["aisdk"] + }, + "package": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": ["type", "package"], + "additionalProperties": false + } + ] + }, + "options": { + "type": "object", + "properties": { + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" + }, + "aisdk": { + "type": "object", + "properties": { + "provider": { + "type": "object" + }, + "request": { + "type": "object" + } + }, + "required": ["provider", "request"], + "additionalProperties": false + } + }, + "required": ["headers", "body", "aisdk"], + "additionalProperties": false + } + }, + "required": ["id", "name", "enabled", "env", "endpoint", "options"], + "additionalProperties": false + }, "EventTuiToastShow1": { "type": "object", "properties": { @@ -19121,6 +19752,14 @@ "name": "v2 messages", "description": "Experimental v2 message routes." }, + { + "name": "v2 models", + "description": "Experimental v2 model routes." + }, + { + "name": "v2 providers", + "description": "Experimental v2 provider routes." + }, { "name": "tui", "description": "Experimental HttpApi TUI routes." diff --git a/specs/v2/provider-model.md b/specs/v2/provider-model.md index fe5a98bdd..fb4598b58 100644 --- a/specs/v2/provider-model.md +++ b/specs/v2/provider-model.md @@ -55,9 +55,13 @@ const UnknownEndpoint = Schema.Struct({ type: Schema.Literal("unknown"), }) -export const Endpoint = Schema.Union([UnknownEndpoint, OpenAIResponses, OpenAICompletions, AnthropicMessages, AISDK]).pipe( - Schema.toTaggedUnion("type"), -) +export const Endpoint = Schema.Union([ + UnknownEndpoint, + OpenAIResponses, + OpenAICompletions, + AnthropicMessages, + AISDK, +]).pipe(Schema.toTaggedUnion("type")) export type Endpoint = typeof Endpoint.Type export const Options = Schema.Struct({ @@ -198,7 +202,6 @@ export class Info extends Schema.Class("ModelV2.Info")({ }) } } - ``` ## Catalog Interface @@ -253,23 +256,21 @@ const available = provider.enabled && model.status !== "deprecated" ## Plugin Interface ```ts -export type Definition = Effect.Effect<{ - readonly order: number - readonly hooks: HookFunctions -}, never, R> +export type Definition = Effect.Effect< + { + readonly order: number + readonly hooks: HookFunctions + }, + never, + R +> export interface Interface { - readonly add: (input: { - id: ID - definition: Definition - }) => Effect.Effect + readonly add: (input: { id: ID; definition: Definition }) => Effect.Effect readonly remove: (id: ID) => Effect.Effect - readonly trigger: ( - name: Name, - input: HookInput, - ) => Effect.Effect> + readonly trigger: (name: Name, input: HookInput) => Effect.Effect> } ``` From e28ef7b57c71aa056d127642209c73e3958cf22c Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 20:18:06 +0530 Subject: [PATCH 262/378] refactor(flags): route sync workspaces through runtime flags (#27336) --- packages/opencode/src/sync/index.ts | 14 ++++++++------ packages/opencode/test/sync/index.test.ts | 18 ++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 5c29101b6..e0ec2d345 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -7,11 +7,11 @@ import type { InstanceContext } from "@/project/instance" import { EventSequenceTable, EventTable } from "./event.sql" import type { WorkspaceID } from "@/control-plane/schema" import { EventID } from "./schema" -import { Flag } from "@opencode-ai/core/flag/flag" import { Context, Effect, Layer, Schema as EffectSchema } from "effect" import type { DeepMutable } from "@opencode-ai/core/schema" import { serviceUse } from "@/effect/service-use" import { InstanceState } from "@/effect/instance-state" +import { RuntimeFlags } from "@/effect/runtime-flags" // Keep `Event["data"]` mutable because projectors mutate the persisted shape // when writing to the database. Bus payloads (`Properties`) stay readonly — @@ -69,6 +69,8 @@ export class Service extends Context.Service()("@opencode/Sy export const layer = Layer.effect(Service)( Effect.gen(function* () { + const flags = yield* RuntimeFlags.Service + const replay: Interface["replay"] = Effect.fn("SyncEvent.replay")(function* (event, options) { const def = registry.get(event.type) if (!def) { @@ -104,7 +106,7 @@ export const layer = Layer.effect(Service)( workspace: yield* InstanceState.workspaceID, } : undefined - process(def, event, { publish, context, ownerID: options?.ownerID }) + process(def, event, { publish, context, ownerID: options?.ownerID, experimentalWorkspaces: flags.experimentalWorkspaces }) }) const replayAll: Interface["replayAll"] = Effect.fn("SyncEvent.replayAll")(function* (events, options) { @@ -160,7 +162,7 @@ export const layer = Layer.effect(Service)( const seq = row?.seq != null ? row.seq + 1 : 0 const event = { id, seq, aggregateID: agg, data } - process(def, event, { publish, context }) + process(def, event, { publish, context, experimentalWorkspaces: flags.experimentalWorkspaces }) }, { behavior: "immediate", @@ -197,7 +199,7 @@ export const layer = Layer.effect(Service)( }), ) -export const defaultLayer = layer +export const defaultLayer = layer.pipe(Layer.provide(RuntimeFlags.defaultLayer)) export const use = serviceUse(Service) @@ -279,7 +281,7 @@ export function project( function process( def: Def, event: Event, - options: { publish: boolean; context?: PublishContext; ownerID?: string }, + options: { publish: boolean; context?: PublishContext; ownerID?: string; experimentalWorkspaces: boolean }, ) { if (projectors == null) { throw new Error("No projectors available. Call `SyncEvent.init` to install projectors") @@ -293,7 +295,7 @@ function process( Database.transaction((tx) => { projector(tx, event.data, event) - if (Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) { + if (options.experimentalWorkspaces) { tx.insert(EventSequenceTable) .values({ aggregate_id: event.aggregateID, diff --git a/packages/opencode/test/sync/index.test.ts b/packages/opencode/test/sync/index.test.ts index 10f593a57..c4e5b8606 100644 --- a/packages/opencode/test/sync/index.test.ts +++ b/packages/opencode/test/sync/index.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, beforeEach, afterEach, afterAll } from "bun:test" +import { describe, expect, beforeEach, afterAll } from "bun:test" import { provideTmpdirInstance } from "../fixture/fixture" import { Effect, Layer, Schema } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" @@ -7,21 +7,19 @@ import { SyncEvent } from "../../src/sync" import { Database, eq } from "@/storage/db" import { EventSequenceTable, EventTable } from "../../src/sync/event.sql" import { MessageID } from "../../src/session/schema" -import { Flag } from "@opencode-ai/core/flag/flag" import { initProjectors } from "../../src/server/projectors" import { testEffect } from "../lib/effect" +import { RuntimeFlags } from "@/effect/runtime-flags" -const original = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES -const it = testEffect(Layer.mergeAll(SyncEvent.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect( + Layer.mergeAll( + SyncEvent.layer.pipe(Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: true }))), + CrossSpawnSpawner.defaultLayer, + ), +) beforeEach(() => { Database.close() - - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true -}) - -afterEach(() => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = original }) describe("SyncEvent", () => { From 72acdf050598e84725e4720180b8c91e649e2b3c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 14:50:34 +0000 Subject: [PATCH 263/378] chore: generate --- packages/opencode/src/sync/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index e0ec2d345..7f9b8eeef 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -106,7 +106,12 @@ export const layer = Layer.effect(Service)( workspace: yield* InstanceState.workspaceID, } : undefined - process(def, event, { publish, context, ownerID: options?.ownerID, experimentalWorkspaces: flags.experimentalWorkspaces }) + process(def, event, { + publish, + context, + ownerID: options?.ownerID, + experimentalWorkspaces: flags.experimentalWorkspaces, + }) }) const replayAll: Interface["replayAll"] = Effect.fn("SyncEvent.replayAll")(function* (events, options) { From 268d7581309dffa408b9979d7f0d3078d5aea5a6 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 20:22:30 +0530 Subject: [PATCH 264/378] refactor(flags): route control-plane workspaces through runtime flags (#27337) --- .../opencode/src/control-plane/workspace.ts | 6 ++- .../test/control-plane/workspace.test.ts | 45 ++++++++++++++----- .../test/plugin/workspace-adapter.test.ts | 16 ++++++- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index e7e65f890..4a21e2e65 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -10,8 +10,8 @@ import { GlobalBus } from "@/bus/global" import { Auth } from "@/auth" import { SyncEvent } from "@/sync" import { EventSequenceTable, EventTable } from "@/sync/event.sql" -import { Flag } from "@opencode-ai/core/flag/flag" import * as Log from "@opencode-ai/core/util/log" +import { RuntimeFlags } from "@/effect/runtime-flags" import { Filesystem } from "@/util/filesystem" import { ProjectID } from "@/project/schema" import { Slug } from "@opencode-ai/core/util/slug" @@ -175,6 +175,7 @@ export const layer = Layer.effect( const http = yield* HttpClient.HttpClient const sync = yield* SyncEvent.Service const vcs = yield* Vcs.Service + const flags = yield* RuntimeFlags.Service const connections = new Map() const syncFibers = yield* FiberMap.make() @@ -482,7 +483,7 @@ export const layer = Layer.effect( }) const startSync = Effect.fn("Workspace.startSync")(function* (space: Info) { - if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return + if (!flags.experimentalWorkspaces) return const adapter = getAdapter(space.projectID, space.type) const target = yield* EffectBridge.fromPromise(() => adapter.target(space)).pipe( @@ -1040,6 +1041,7 @@ export const defaultLayer = layer.pipe( Layer.provide(Project.defaultLayer), Layer.provide(Vcs.defaultLayer), Layer.provide(FetchHttpClient.layer), + Layer.provide(RuntimeFlags.defaultLayer), ) const TIMEOUT = 5000 diff --git a/packages/opencode/test/control-plane/workspace.test.ts b/packages/opencode/test/control-plane/workspace.test.ts index adac51fe5..01304e805 100644 --- a/packages/opencode/test/control-plane/workspace.test.ts +++ b/packages/opencode/test/control-plane/workspace.test.ts @@ -6,7 +6,7 @@ import path from "node:path" import { setTimeout as delay } from "node:timers/promises" import { NodeHttpServer } from "@effect/platform-node" import { Effect, Layer, Schema } from "effect" -import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" +import { FetchHttpClient, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" import { eq } from "drizzle-orm" import * as Log from "@opencode-ai/core/util/log" import { Flag } from "@opencode-ai/core/flag/flag" @@ -33,24 +33,43 @@ import * as Workspace from "../../src/control-plane/workspace" import { AppRuntime } from "@/effect/app-runtime" import { InstanceStore } from "@/project/instance-store" import { InstanceBootstrap } from "@/project/bootstrap" +import { Auth } from "@/auth" +import { SessionPrompt } from "@/session/prompt" +import { Project } from "@/project/project" +import { Vcs } from "@/project/vcs" +import { RuntimeFlags } from "@/effect/runtime-flags" void Log.init({ print: false }) -const testServerLayer = Layer.mergeAll( - NodeHttpServer.layer(Http.createServer, { host: "127.0.0.1", port: 0 }), - Workspace.defaultLayer.pipe(Layer.provide(InstanceStore.defaultLayer), Layer.provide(InstanceBootstrap.defaultLayer)), - SessionNs.defaultLayer, -) -const it = testEffect(testServerLayer) - const originalWorkspacesFlag = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES const originalEnv = { OPENCODE_AUTH_CONTENT: process.env.OPENCODE_AUTH_CONTENT, + OPENCODE_EXPERIMENTAL_WORKSPACES: process.env.OPENCODE_EXPERIMENTAL_WORKSPACES, OTEL_EXPORTER_OTLP_HEADERS: process.env.OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_RESOURCE_ATTRIBUTES: process.env.OTEL_RESOURCE_ATTRIBUTES, } +const workspaceLayer = (experimentalWorkspaces: boolean) => + Workspace.layer.pipe( + Layer.provide(Auth.defaultLayer), + Layer.provide(SessionNs.defaultLayer), + Layer.provide(SyncEvent.defaultLayer), + Layer.provide(SessionPrompt.defaultLayer), + Layer.provide(Project.defaultLayer), + Layer.provide(Vcs.defaultLayer), + Layer.provide(FetchHttpClient.layer), + Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces })), + Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))), + ) + +const testServerLayer = Layer.mergeAll( + NodeHttpServer.layer(Http.createServer, { host: "127.0.0.1", port: 0 }), + workspaceLayer(true), + SessionNs.defaultLayer, +) +const it = testEffect(testServerLayer) + type RecordedCreate = { info: WorkspaceInfo env: Record @@ -94,6 +113,7 @@ beforeEach(() => { Database.close() Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true restoreEnv() + process.env.OPENCODE_EXPERIMENTAL_WORKSPACES = "true" }) afterEach(async () => { @@ -141,6 +161,12 @@ const isWorkspaceSyncing = (id: WorkspaceID) => const startWorkspaceSyncing = (projectID: ProjectID) => { void runWorkspace(Workspace.Service.use((workspace) => workspace.startWorkspaceSyncing(projectID))) } +const startWorkspaceSyncingWithFlag = (projectID: ProjectID, experimentalWorkspaces: boolean) => + Effect.runPromise( + Workspace.Service.use((workspace) => workspace.startWorkspaceSyncing(projectID)).pipe( + Effect.provide(workspaceLayer(experimentalWorkspaces)), + ), + ) const waitForWorkspaceSync = (workspaceID: WorkspaceID, state: Record, signal?: AbortSignal) => runWorkspace(Workspace.Service.use((workspace) => workspace.waitForSync(workspaceID, state, signal))) @@ -980,7 +1006,6 @@ describe("workspace CRUD", () => { describe("workspace sync state", () => { test("startWorkspaceSyncing is disabled by the experimental workspace flag", async () => { await withInstance(async (dir) => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false const type = unique("flag-disabled") const info = workspaceInfo(Instance.project.id, type) const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({}))) @@ -988,7 +1013,7 @@ describe("workspace sync state", () => { insertWorkspace(info) registerAdapter(Instance.project.id, type, localAdapter(path.join(dir, "flag-disabled")).adapter) - startWorkspaceSyncing(Instance.project.id) + await startWorkspaceSyncingWithFlag(Instance.project.id, false) await delay(25) expect((await workspaceStatus()).find((item) => item.workspaceID === info.id)?.status).toBeUndefined() diff --git a/packages/opencode/test/plugin/workspace-adapter.test.ts b/packages/opencode/test/plugin/workspace-adapter.test.ts index bef860432..0cf603fa3 100644 --- a/packages/opencode/test/plugin/workspace-adapter.test.ts +++ b/packages/opencode/test/plugin/workspace-adapter.test.ts @@ -1,5 +1,6 @@ import { afterAll, afterEach, describe, expect } from "bun:test" import { Effect, Layer, Option } from "effect" +import { FetchHttpClient } from "effect/unstable/http" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" @@ -17,6 +18,11 @@ import { Plugin } from "../../src/plugin/index" import { InstanceBootstrap } from "../../src/project/bootstrap-service" import { Instance } from "../../src/project/instance" import { InstanceStore } from "../../src/project/instance-store" +import { Project } from "../../src/project/project" +import { Vcs } from "../../src/project/vcs" +import { Session } from "../../src/session/session" +import { SessionPrompt } from "../../src/session/prompt" +import { SyncEvent } from "../../src/sync" import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { NpmTest } from "../fake/npm" @@ -42,8 +48,16 @@ const pluginLayer = Plugin.layer.pipe( Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })), ) const noopBootstrapLayer = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) -const workspaceLayer = Workspace.defaultLayer.pipe( +const workspaceLayer = Workspace.layer.pipe( + Layer.provide(Auth.defaultLayer), + Layer.provide(Session.defaultLayer), + Layer.provide(SyncEvent.defaultLayer), + Layer.provide(SessionPrompt.defaultLayer), + Layer.provide(Project.defaultLayer), + Layer.provide(Vcs.defaultLayer), + Layer.provide(FetchHttpClient.layer), Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrapLayer))), + Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: true })), ) const it = testEffect(Layer.mergeAll(pluginLayer, workspaceLayer, CrossSpawnSpawner.defaultLayer)) From 0b112e5bcf10a0ff0b0118a359a6c9bd80d48899 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 10:56:26 -0400 Subject: [PATCH 265/378] test: migrate permission task config tests (#27343) --- .../opencode/test/permission-task.test.ts | 174 +++++++++--------- 1 file changed, 82 insertions(+), 92 deletions(-) diff --git a/packages/opencode/test/permission-task.test.ts b/packages/opencode/test/permission-task.test.ts index 64b93bb8b..f2084b095 100644 --- a/packages/opencode/test/permission-task.test.ts +++ b/packages/opencode/test/permission-task.test.ts @@ -1,16 +1,12 @@ -import { afterEach, describe, test, expect } from "bun:test" +import { describe, test, expect } from "bun:test" +import { Effect } from "effect" import { Permission } from "../src/permission" import { Config } from "@/config/config" -import { Instance } from "../src/project/instance" -import { WithInstance } from "../src/project/with-instance" -import { disposeAllInstances, tmpdir } from "./fixture/fixture" -import { AppRuntime } from "../src/effect/app-runtime" +import { testEffect } from "./lib/effect" -const load = () => AppRuntime.runPromise(Config.Service.use((svc) => svc.get())) +const it = testEffect(Config.defaultLayer) -afterEach(async () => { - await disposeAllInstances() -}) +const load = Config.Service.use((svc) => svc.get()) describe("Permission.evaluate for permission.task", () => { const createRuleset = (rules: Record): Permission.Ruleset => @@ -147,99 +143,83 @@ describe("Permission.disabled for task tool", () => { // Integration tests that load permissions from real config files describe("permission.task with real config files", () => { - test("loads task permissions from opencode.json config", async () => { - await using tmp = await tmpdir({ - git: true, - config: { - permission: { - task: { - "*": "allow", - "code-reviewer": "deny", - }, - }, - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() + it.instance( + "loads task permissions from opencode.json config", + () => + Effect.gen(function* () { + const config = yield* load const ruleset = Permission.fromConfig(config.permission ?? {}) // general and orchestrator-fast should be allowed, code-reviewer denied expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow") expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny") - }, - }) - }) - - test("loads task permissions with wildcard patterns from config", async () => { - await using tmp = await tmpdir({ + }), + { git: true, config: { permission: { task: { - "*": "ask", - "orchestrator-*": "deny", + "*": "allow", + "code-reviewer": "deny", }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() + }, + ) + + it.instance( + "loads task permissions with wildcard patterns from config", + () => + Effect.gen(function* () { + const config = yield* load const ruleset = Permission.fromConfig(config.permission ?? {}) // general and code-reviewer should be ask, orchestrator-* denied expect(Permission.evaluate("task", "general", ruleset).action).toBe("ask") expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask") expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("deny") - }, - }) - }) - - test("evaluate respects task permission from config", async () => { - await using tmp = await tmpdir({ + }), + { git: true, config: { permission: { task: { - general: "allow", - "code-reviewer": "deny", + "*": "ask", + "orchestrator-*": "deny", }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() + }, + ) + + it.instance( + "evaluate respects task permission from config", + () => + Effect.gen(function* () { + const config = yield* load const ruleset = Permission.fromConfig(config.permission ?? {}) expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny") // Unspecified agents default to "ask" expect(Permission.evaluate("task", "unknown-agent", ruleset).action).toBe("ask") - }, - }) - }) - - test("mixed permission config with task and other tools", async () => { - await using tmp = await tmpdir({ + }), + { git: true, config: { permission: { - bash: "allow", - edit: "ask", task: { - "*": "deny", general: "allow", + "code-reviewer": "deny", }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() + }, + ) + + it.instance( + "mixed permission config with task and other tools", + () => + Effect.gen(function* () { + const config = yield* load const ruleset = Permission.fromConfig(config.permission ?? {}) // Verify task permissions @@ -257,27 +237,27 @@ describe("permission.task with real config files", () => { // task is NOT disabled because disabled() uses findLast, and the last rule // matching "task" permission is {pattern: "general", action: "allow"}, not pattern: "*" expect(disabled.has("task")).toBe(false) - }, - }) - }) - - test("task tool disabled when global deny comes last in config", async () => { - await using tmp = await tmpdir({ + }), + { git: true, config: { permission: { + bash: "allow", + edit: "ask", task: { - general: "allow", - "code-reviewer": "allow", "*": "deny", + general: "allow", }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() + }, + ) + + it.instance( + "task tool disabled when global deny comes last in config", + () => + Effect.gen(function* () { + const config = yield* load const ruleset = Permission.fromConfig(config.permission ?? {}) // Last matching rule wins - "*" deny is last, so all agents are denied @@ -289,26 +269,26 @@ describe("permission.task with real config files", () => { // and sees pattern: "*" with action: "deny", so task is disabled const disabled = Permission.disabled(["task"], ruleset) expect(disabled.has("task")).toBe(true) - }, - }) - }) - - test("task tool NOT disabled when specific allow comes last in config", async () => { - await using tmp = await tmpdir({ + }), + { git: true, config: { permission: { task: { - "*": "deny", general: "allow", + "code-reviewer": "allow", + "*": "deny", }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() + }, + ) + + it.instance( + "task tool NOT disabled when specific allow comes last in config", + () => + Effect.gen(function* () { + const config = yield* load const ruleset = Permission.fromConfig(config.permission ?? {}) // Evaluate uses findLast - "general" allow comes after "*" deny @@ -321,7 +301,17 @@ describe("permission.task with real config files", () => { // So the task tool is NOT disabled (even though most subagents are denied) const disabled = Permission.disabled(["task"], ruleset) expect(disabled.has("task")).toBe(false) + }), + { + git: true, + config: { + permission: { + task: { + "*": "deny", + general: "allow", + }, + }, }, - }) - }) + }, + ) }) From 74046648271cf5f6229b61f281dd38cc3f4c0cd7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 13 May 2026 10:56:51 -0400 Subject: [PATCH 266/378] refactor: migrate installation tests to testEffect (#27342) --- .../test/installation/installation.test.ts | 177 +++++++++--------- 1 file changed, 88 insertions(+), 89 deletions(-) diff --git a/packages/opencode/test/installation/installation.test.ts b/packages/opencode/test/installation/installation.test.ts index 5b26b0565..9ca38e968 100644 --- a/packages/opencode/test/installation/installation.test.ts +++ b/packages/opencode/test/installation/installation.test.ts @@ -1,9 +1,10 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect } from "bun:test" import { Effect, Layer, Stream } from "effect" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { Installation } from "../../src/installation" import { InstallationChannel } from "@opencode-ai/core/installation/version" +import { testEffect } from "../lib/effect" const encoder = new TextEncoder() @@ -51,86 +52,84 @@ function testLayer( describe("installation", () => { describe("latest", () => { - test("reads release version from GitHub releases", async () => { - const layer = testLayer(() => jsonResponse({ tag_name: "v1.2.3" })) - - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("unknown")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("1.2.3") - }) - - test("strips v prefix from GitHub release tag", async () => { - const layer = testLayer(() => jsonResponse({ tag_name: "v4.0.0-beta.1" })) + testEffect(testLayer(() => jsonResponse({ tag_name: "v1.2.3" }))).effect( + "reads release version from GitHub releases", + () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("unknown")) + expect(result).toBe("1.2.3") + }), + ) - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("curl")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("4.0.0-beta.1") - }) + testEffect(testLayer(() => jsonResponse({ tag_name: "v4.0.0-beta.1" }))).effect( + "strips v prefix from GitHub release tag", + () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("curl")) + expect(result).toBe("4.0.0-beta.1") + }), + ) - test("reads npm versions via registry", async () => { - const calls: string[] = [] - const layer = testLayer((request) => { - calls.push(request.url) + const npmCalls: string[] = [] + testEffect( + testLayer((request) => { + npmCalls.push(request.url) return jsonResponse({ version: "1.5.0" }) - }) - - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("npm")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("1.5.0") - expect(calls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`) - }) + }), + ).effect("reads npm versions via registry", () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("npm")) + expect(result).toBe("1.5.0") + expect(npmCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`) + }), + ) - test("reads bun versions via registry", async () => { - const calls: string[] = [] - const layer = testLayer((request) => { - calls.push(request.url) + const bunCalls: string[] = [] + testEffect( + testLayer((request) => { + bunCalls.push(request.url) return jsonResponse({ version: "1.6.0" }) - }) - - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("bun")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("1.6.0") - expect(calls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`) - }) + }), + ).effect("reads bun versions via registry", () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("bun")) + expect(result).toBe("1.6.0") + expect(bunCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`) + }), + ) - test("reads pnpm versions via registry", async () => { - const calls: string[] = [] - const layer = testLayer((request) => { - calls.push(request.url) + const pnpmCalls: string[] = [] + testEffect( + testLayer((request) => { + pnpmCalls.push(request.url) return jsonResponse({ version: "1.7.0" }) - }) - - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("pnpm")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("1.7.0") - expect(calls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`) - }) - - test("reads scoop manifest versions", async () => { - const layer = testLayer(() => jsonResponse({ version: "2.3.4" })) - - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("scoop")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("2.3.4") - }) + }), + ).effect("reads pnpm versions via registry", () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("pnpm")) + expect(result).toBe("1.7.0") + expect(pnpmCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`) + }), + ) - test("reads chocolatey feed versions", async () => { - const layer = testLayer(() => jsonResponse({ d: { results: [{ Version: "3.4.5" }] } })) + testEffect(testLayer(() => jsonResponse({ version: "2.3.4" }))).effect("reads scoop manifest versions", () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("scoop")) + expect(result).toBe("2.3.4") + }), + ) - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("choco")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("3.4.5") - }) + testEffect(testLayer(() => jsonResponse({ d: { results: [{ Version: "3.4.5" }] } }))).effect( + "reads chocolatey feed versions", + () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("choco")) + expect(result).toBe("3.4.5") + }), + ) - test("reads brew formulae API versions", async () => { - const layer = testLayer( + testEffect( + testLayer( () => jsonResponse({ versions: { stable: "2.0.0" } }), (cmd, args) => { // getBrewFormula: return core formula (no tap) @@ -138,31 +137,31 @@ describe("installation", () => { if (cmd === "brew" && args.includes("--formula") && args.includes("opencode")) return "opencode" return "" }, - ) + ), + ).effect("reads brew formulae API versions", () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("brew")) + expect(result).toBe("2.0.0") + }), + ) - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("brew")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("2.0.0") + const brewInfoJson = JSON.stringify({ + formulae: [{ versions: { stable: "2.1.0" } }], }) - - test("reads brew tap info JSON via CLI", async () => { - const brewInfoJson = JSON.stringify({ - formulae: [{ versions: { stable: "2.1.0" } }], - }) - const layer = testLayer( + testEffect( + testLayer( () => jsonResponse({}), // HTTP not used for tap formula (cmd, args) => { if (cmd === "brew" && args.includes("anomalyco/tap/opencode") && args.includes("--formula")) return "opencode" if (cmd === "brew" && args.includes("--json=v2")) return brewInfoJson return "" }, - ) - - const result = await Effect.runPromise( - Installation.Service.use((svc) => svc.latest("brew")).pipe(Effect.provide(layer)), - ) - expect(result).toBe("2.1.0") - }) + ), + ).effect("reads brew tap info JSON via CLI", () => + Effect.gen(function* () { + const result = yield* Installation.Service.use((svc) => svc.latest("brew")) + expect(result).toBe("2.1.0") + }), + ) }) }) From d43124abe029adfd2fbf7d516a1c6dfa7a0844b0 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 13 May 2026 10:57:22 -0400 Subject: [PATCH 267/378] ignore: notes --- specs/v2/api.html | 781 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 781 insertions(+) create mode 100644 specs/v2/api.html diff --git a/specs/v2/api.html b/specs/v2/api.html new file mode 100644 index 000000000..c23d7d4f0 --- /dev/null +++ b/specs/v2/api.html @@ -0,0 +1,781 @@ + + + + + + opencode v2 API + + + + + + From e7aed649493a668b5b09baac4251969667cf7a20 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 14:59:13 +0000 Subject: [PATCH 268/378] chore: generate --- specs/v2/api.html | 750 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 565 insertions(+), 185 deletions(-) diff --git a/specs/v2/api.html b/specs/v2/api.html index c23d7d4f0..147d24f58 100644 --- a/specs/v2/api.html +++ b/specs/v2/api.html @@ -17,7 +17,13 @@ --accent: #496b5a; --accent-soft: #dce7dc; font-family: - Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; } * { @@ -34,8 +40,7 @@ background: radial-gradient(circle at 12% 0%, rgba(73, 107, 90, 0.12), transparent 34rem), linear-gradient(90deg, rgba(38, 52, 47, 0.055) 1px, transparent 1px), - linear-gradient(rgba(38, 52, 47, 0.045) 1px, transparent 1px), - var(--bg); + linear-gradient(rgba(38, 52, 47, 0.045) 1px, transparent 1px), var(--bg); background-size: 72px 72px; color: var(--fg); line-height: 1.5; @@ -231,7 +236,13 @@ .diagram text { fill: var(--fg); font-family: - Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; } .diagram .box { @@ -412,7 +423,10 @@

API map

- Everything has one canonical route. Some routes are server-scoped; runtime routes use context; session item routes use the session. + Everything has one canonical route. Some routes are server-scoped; runtime routes use context; session item + routes use the session.

Server-scoped routes manage the whole server: projects, workspace lifecycle, and auth accounts. Runtime context is for anything resolved from an active directory, including config, provider capabilities, tools, @@ -432,7 +446,9 @@

API map

Context Model

API context resolution - Non-session routes resolve from request context, session item routes resolve from session storage. + + Non-session routes resolve from request context, session item routes resolve from session storage. + @@ -468,8 +484,8 @@

Context Model

Request-context calls

- These calls operate against a directory, optionally through a workspace. Simple clients omit context and - use the default runtime. + These calls operate against a directory, optionally through a workspace. Simple clients omit context and use + the default runtime.

GET /api/fs/tree?path=.&directory=/repo/app&workspace=ws_123
@@ -491,187 +507,551 @@

Session-pinned calls

Operation Inventory

- The SDK is the source of truth. HTTP routes are mounts for RPC-style operations. server operations do not use runtime context. request operations use request/default runtime context from directory and workspace query parameters. session operations use pinned session context and should not accept context input. + The SDK is the source of truth. HTTP routes are mounts for RPC-style operations. + server operations do not use runtime context. + request operations use request/default runtime context from + directory and workspace query parameters. + session operations use pinned session context and should not accept + context input.

- + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OperationInputContextHTTP mountPurpose
OperationInputContextHTTP mountPurpose
agent.list{}requestGET /api/agentAvailable agents.
auth.activate{ accountID: AccountID }serverPOST /api/auth/:accountID/activateSet the account as active for its service.
auth.create{ - serviceID: ServiceID - credential: - | { type: "oauth", refresh: string, access: string, expires: number } - | { type: "api", key: string, metadata?: Record<string, string> } - description?: string - active?: boolean -}serverPOST /api/authCreate an auth account.
auth.delete{ accountID: AccountID }serverDELETE /api/auth/:accountIDRemove an auth account.
auth.get{ accountID: AccountID }serverGET /api/auth/:accountIDGet one auth account.
auth.list{ serviceID?: ServiceID }serverGET /api/authList saved auth accounts. Response includes active account mapping.
auth.update{ - accountID: AccountID - description?: string - credential?: - | { type: "oauth", refresh: string, access: string, expires: number } - | { type: "api", key: string, metadata?: Record<string, string> } -}serverPATCH /api/auth/:accountIDUpdate account description or credential.
catalog.model.get{ - providerID: ProviderID - modelID: ModelID -}serverGET /api/catalog/model/:providerID/:modelIDGet one catalog model.
catalog.model.list{}serverGET /api/catalog/modelList flattened catalog models.
command.list{}requestGET /api/commandAvailable commands.
config.get{}requestGET /api/configResolved config.
config.update{ config: Config }requestPATCH /api/configUpdate config.
event.subscribe{}requestGET /api/eventServer-sent events for the resolved runtime context.
formatter.status{}requestGET /api/formatterFormatter status.
fs.file{ path: string }requestGET /api/fs/fileRead one file.
fs.grep{ - pattern: string - include?: string - limit?: number -}requestPOST /api/fs/grepSearch file contents.
fs.search{ - query: string - type?: "file" | "directory" - limit?: number -}requestPOST /api/fs/searchSearch paths by name.
fs.tree{ path: string }requestGET /api/fs/treeBrowse a directory.
lsp.status{}requestGET /api/lspLSP status.
mcp.prompt.list{}requestGET /api/mcp/promptList MCP prompts.
mcp.prompt.render{ - server: string - name: string - arguments?: Record<string, string> -}requestPOST /api/mcp/prompt/renderRender one MCP prompt.
mcp.resource.list{}requestGET /api/mcp/resourceList MCP resources.
mcp.resource.read{ - server: string - uri: string -}requestGET /api/mcp/resource/readRead one MCP resource.
mcp.server.create{ - name: string - config: - | { type: "local", command: string, arguments?: string[], environment?: Record<string, string> } - | { type: "remote", url: string, headers?: Record<string, string>, oauth?: boolean | object } -}requestPOST /api/mcp/serverAdd an MCP server to runtime config.
mcp.server.list{}requestGET /api/mcp/serverList MCP servers with status and auth state.
mcp.server.oauth.callback{ - name: string - code: string -}requestPOST /api/mcp/server/:name/oauth/callbackComplete MCP OAuth.
mcp.server.oauth.delete{ name: string }requestDELETE /api/mcp/server/:name/oauthRemove MCP OAuth credentials.
mcp.server.oauth.start{ name: string }requestPOST /api/mcp/server/:name/oauthStart MCP OAuth.
permission.list{}requestGET /api/permissionPending permission requests.
permission.reply{ - permissionID: PermissionID - response: PermissionReply -}requestPOST /api/permission/:permissionID/replyReply to a permission request.
project.get{ projectID: ProjectID }serverGET /api/project/:projectIDGet project metadata.
project.list{}serverGET /api/projectList projects known to this server.
project.update{ - projectID: ProjectID - name?: string - icon?: string - commands?: Array<{ - name: string - command: string - }> -}serverPATCH /api/project/:projectIDUpdate project metadata.
provider.list{}requestGET /api/providerProvider inventory for the runtime context.
pty.create{ - command?: string - cwd?: string - shell?: string -}requestPOST /api/ptyCreate PTY in the runtime context.
pty.delete{ ptyID: PtyID }requestDELETE /api/pty/:ptyIDDelete PTY.
pty.get{ ptyID: PtyID }requestGET /api/pty/:ptyIDGet PTY info.
pty.list{}requestGET /api/ptyList PTYs for the runtime.
pty.update{ - ptyID: PtyID - title?: string - size?: { columns: number, rows: number } -}requestPATCH /api/pty/:ptyIDUpdate PTY.
question.list{}requestGET /api/questionPending user questions.
question.reject{ questionID: QuestionID }requestPOST /api/question/:questionID/rejectReject a question.
question.reply{ - questionID: QuestionID - response: QuestionResponse -}requestPOST /api/question/:questionID/replyReply to a question.
session.compact{ sessionID: SessionID }sessionPOST /api/session/:sessionID/compactCompact the session conversation.
session.context{ sessionID: SessionID }sessionGET /api/session/:sessionID/contextReturn active context messages after the last compaction.
session.create{ - title?: string - agent?: string - model?: { providerID: ProviderID, modelID: ModelID } - permission?: PermissionRule[] -}requestPOST /api/sessionCreate a session pinned to resolved runtime context.
session.delete{ sessionID: SessionID }sessionDELETE /api/session/:sessionIDDelete a session.
session.diff{ sessionID: SessionID }sessionGET /api/session/:sessionID/diffReturn session diff summary.
session.get{ sessionID: SessionID }sessionGET /api/session/:sessionIDGet one session.
session.list{ - limit?: number - order?: "asc" | "desc" - path?: string - roots?: boolean - start?: number - search?: string - cursor?: string -}requestGET /api/sessionList sessions for the current runtime context by default.
session.message.list{ - sessionID: SessionID - limit?: number - order?: "asc" | "desc" - cursor?: string -}sessionGET /api/session/:sessionID/messagePage through session messages.
session.prompt{ - sessionID: SessionID - prompt: Prompt - delivery?: "immediate" | "deferred" -}sessionPOST /api/session/:sessionID/promptCreate a user message and queue the agent loop.
session.todo{ sessionID: SessionID }sessionGET /api/session/:sessionID/todoReturn todos associated with the session.
session.update{ - sessionID: SessionID - title?: string - archived?: number - permission?: PermissionRule[] -}sessionPATCH /api/session/:sessionIDUpdate title, archival state, or session metadata.
session.wait{ sessionID: SessionID }sessionPOST /api/session/:sessionID/waitWait until the session is idle.
skill.list{}requestGET /api/skillAvailable skills.
vcs.diff{ - format?: "json" | "patch" - mode?: "worktree" | "default" -}requestGET /api/vcs/diffDiff for the runtime directory.
vcs.get{}requestGET /api/vcsVCS metadata.
vcs.patch{ patch: string }requestPOST /api/vcs/patchApply a patch to the runtime directory.
vcs.status{}requestGET /api/vcs/statusChanged files.
workspace.create{ - projectID?: ProjectID - name?: string - directory?: string - type: string - metadata?: Record<string, unknown> -}serverPOST /api/workspaceCreate or register a workspace.
workspace.delete{ workspaceID: WorkspaceID }serverDELETE /api/workspace/:workspaceIDRemove a workspace registration.
workspace.get{ workspaceID: WorkspaceID }serverGET /api/workspace/:workspaceIDGet workspace metadata.
workspace.list{ projectID?: ProjectID }serverGET /api/workspaceList workspaces, optionally filtered by project.
workspace.status{}serverGET /api/workspace/statusConnection/lifecycle status for all workspaces. Needs team discussion.
workspace.sync{}serverPOST /api/workspace/syncSync workspace metadata from adapters. Needs team discussion.
workspace.update{ - workspaceID: WorkspaceID - name?: string - metadata?: Record<string, unknown> - archived?: boolean -}serverPATCH /api/workspace/:workspaceIDUpdate workspace metadata or lifecycle state.
workspace.warp{ - workspaceID?: WorkspaceID - sessionID: SessionID - copyChanges: boolean -}serverPOST /api/workspace/warpMove a session into or out of a workspace. Needs team discussion.
agent.list{}requestGET /api/agentAvailable agents.
auth.activate{ accountID: AccountID }serverPOST /api/auth/:accountID/activateSet the account as active for its service.
auth.create + { serviceID: ServiceID credential: | { type: "oauth", refresh: string, access: string, expires: + number } | { type: "api", key: string, metadata?: Record<string, string> } description?: + string active?: boolean } + serverPOST /api/authCreate an auth account.
auth.delete{ accountID: AccountID }serverDELETE /api/auth/:accountIDRemove an auth account.
auth.get{ accountID: AccountID }serverGET /api/auth/:accountIDGet one auth account.
auth.list{ serviceID?: ServiceID }serverGET /api/authList saved auth accounts. Response includes active account mapping.
auth.update + { accountID: AccountID description?: string credential?: | { type: "oauth", refresh: string, + access: string, expires: number } | { type: "api", key: string, metadata?: Record<string, + string> } } + serverPATCH /api/auth/:accountIDUpdate account description or credential.
catalog.model.get{ providerID: ProviderID modelID: ModelID }serverGET /api/catalog/model/:providerID/:modelIDGet one catalog model.
catalog.model.list{}serverGET /api/catalog/modelList flattened catalog models.
command.list{}requestGET /api/commandAvailable commands.
config.get{}requestGET /api/configResolved config.
config.update{ config: Config }requestPATCH /api/configUpdate config.
event.subscribe{}requestGET /api/eventServer-sent events for the resolved runtime context.
formatter.status{}requestGET /api/formatterFormatter status.
fs.file{ path: string }requestGET /api/fs/fileRead one file.
fs.grep{ pattern: string include?: string limit?: number }requestPOST /api/fs/grepSearch file contents.
fs.search{ query: string type?: "file" | "directory" limit?: number }requestPOST /api/fs/searchSearch paths by name.
fs.tree{ path: string }requestGET /api/fs/treeBrowse a directory.
lsp.status{}requestGET /api/lspLSP status.
mcp.prompt.list{}requestGET /api/mcp/promptList MCP prompts.
mcp.prompt.render + { server: string name: string arguments?: Record<string, string> } + requestPOST /api/mcp/prompt/renderRender one MCP prompt.
mcp.resource.list{}requestGET /api/mcp/resourceList MCP resources.
mcp.resource.read{ server: string uri: string }requestGET /api/mcp/resource/readRead one MCP resource.
mcp.server.create + { name: string config: | { type: "local", command: string, arguments?: string[], environment?: + Record<string, string> } | { type: "remote", url: string, headers?: Record<string, + string>, oauth?: boolean | object } } + requestPOST /api/mcp/serverAdd an MCP server to runtime config.
mcp.server.list{}requestGET /api/mcp/serverList MCP servers with status and auth state.
mcp.server.oauth.callback{ name: string code: string }requestPOST /api/mcp/server/:name/oauth/callbackComplete MCP OAuth.
mcp.server.oauth.delete{ name: string }requestDELETE /api/mcp/server/:name/oauthRemove MCP OAuth credentials.
mcp.server.oauth.start{ name: string }requestPOST /api/mcp/server/:name/oauthStart MCP OAuth.
permission.list{}requestGET /api/permissionPending permission requests.
permission.reply{ permissionID: PermissionID response: PermissionReply }requestPOST /api/permission/:permissionID/replyReply to a permission request.
project.get{ projectID: ProjectID }serverGET /api/project/:projectIDGet project metadata.
project.list{}serverGET /api/projectList projects known to this server.
project.update + { projectID: ProjectID name?: string icon?: string commands?: Array<{ name: string command: + string }> } + serverPATCH /api/project/:projectIDUpdate project metadata.
provider.list{}requestGET /api/providerProvider inventory for the runtime context.
pty.create{ command?: string cwd?: string shell?: string }requestPOST /api/ptyCreate PTY in the runtime context.
pty.delete{ ptyID: PtyID }requestDELETE /api/pty/:ptyIDDelete PTY.
pty.get{ ptyID: PtyID }requestGET /api/pty/:ptyIDGet PTY info.
pty.list{}requestGET /api/ptyList PTYs for the runtime.
pty.update + { ptyID: PtyID title?: string size?: { columns: number, rows: number } } + requestPATCH /api/pty/:ptyIDUpdate PTY.
question.list{}requestGET /api/questionPending user questions.
question.reject{ questionID: QuestionID }requestPOST /api/question/:questionID/rejectReject a question.
question.reply{ questionID: QuestionID response: QuestionResponse }requestPOST /api/question/:questionID/replyReply to a question.
session.compact{ sessionID: SessionID }sessionPOST /api/session/:sessionID/compactCompact the session conversation.
session.context{ sessionID: SessionID }sessionGET /api/session/:sessionID/contextReturn active context messages after the last compaction.
session.create + { title?: string agent?: string model?: { providerID: ProviderID, modelID: ModelID } permission?: + PermissionRule[] } + requestPOST /api/sessionCreate a session pinned to resolved runtime context.
session.delete{ sessionID: SessionID }sessionDELETE /api/session/:sessionIDDelete a session.
session.diff{ sessionID: SessionID }sessionGET /api/session/:sessionID/diffReturn session diff summary.
session.get{ sessionID: SessionID }sessionGET /api/session/:sessionIDGet one session.
session.list + { limit?: number order?: "asc" | "desc" path?: string roots?: boolean start?: number search?: + string cursor?: string } + requestGET /api/sessionList sessions for the current runtime context by default.
session.message.list + { sessionID: SessionID limit?: number order?: "asc" | "desc" cursor?: string } + sessionGET /api/session/:sessionID/messagePage through session messages.
session.prompt + { sessionID: SessionID prompt: Prompt delivery?: "immediate" | "deferred" } + sessionPOST /api/session/:sessionID/promptCreate a user message and queue the agent loop.
session.todo{ sessionID: SessionID }sessionGET /api/session/:sessionID/todoReturn todos associated with the session.
session.update + { sessionID: SessionID title?: string archived?: number permission?: PermissionRule[] } + sessionPATCH /api/session/:sessionIDUpdate title, archival state, or session metadata.
session.wait{ sessionID: SessionID }sessionPOST /api/session/:sessionID/waitWait until the session is idle.
skill.list{}requestGET /api/skillAvailable skills.
vcs.diff{ format?: "json" | "patch" mode?: "worktree" | "default" }requestGET /api/vcs/diffDiff for the runtime directory.
vcs.get{}requestGET /api/vcsVCS metadata.
vcs.patch{ patch: string }requestPOST /api/vcs/patchApply a patch to the runtime directory.
vcs.status{}requestGET /api/vcs/statusChanged files.
workspace.create + { projectID?: ProjectID name?: string directory?: string type: string metadata?: Record<string, + unknown> } + serverPOST /api/workspaceCreate or register a workspace.
workspace.delete{ workspaceID: WorkspaceID }serverDELETE /api/workspace/:workspaceIDRemove a workspace registration.
workspace.get{ workspaceID: WorkspaceID }serverGET /api/workspace/:workspaceIDGet workspace metadata.
workspace.list{ projectID?: ProjectID }serverGET /api/workspaceList workspaces, optionally filtered by project.
workspace.status{}serverGET /api/workspace/statusConnection/lifecycle status for all workspaces. Needs team discussion.
workspace.sync{}serverPOST /api/workspace/syncSync workspace metadata from adapters. Needs team discussion.
workspace.update + { workspaceID: WorkspaceID name?: string metadata?: Record<string, unknown> archived?: + boolean } + serverPATCH /api/workspace/:workspaceIDUpdate workspace metadata or lifecycle state.
workspace.warp + { workspaceID?: WorkspaceID sessionID: SessionID copyChanges: boolean } + serverPOST /api/workspace/warpMove a session into or out of a workspace. Needs team discussion.
@@ -681,8 +1061,8 @@

Operation Inventory

Event Envelope

- Every event uses the same envelope. Resource identity belongs in payload. Runtime identity belongs - in context. + Every event uses the same envelope. Resource identity belongs in payload. Runtime identity + belongs in context.

type ApiEvent<Payload> = {

From ca17ca85cd3ca2fd12e4926b27788e9921acd2c4 Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]" 
Date: Wed, 13 May 2026 15:02:16 +0000
Subject: [PATCH 269/378] 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 0bba38a2c..876b96960 100644
--- a/nix/hashes.json
+++ b/nix/hashes.json
@@ -1,8 +1,8 @@
 {
   "nodeModules": {
-    "x86_64-linux": "sha256-xZyIgqow1wVh0Kfpb5GLUUHsE3jyfqJfrZ9Qykml008=",
-    "aarch64-linux": "sha256-tbbne63KImq4EQrPi45l9YG1dY/SO7b1ZKkLjDfZhWg=",
-    "aarch64-darwin": "sha256-PYsiSMkASbcZxqMXb7UfbkRTiQae6xzseMNhDP+/y5g=",
-    "x86_64-darwin": "sha256-Qnj9FAgXWyiB6U5NyIsRw7aNVNexAagETr07Jwde908="
+    "x86_64-linux": "sha256-cRhvzZoW6gBbE0sQm1+e+6/WgajuA6MSIL5iroFsfqs=",
+    "aarch64-linux": "sha256-0knZfxBULqkt5u6sXFx+a/vqw2rc6IC1+LeAd4TNFhM=",
+    "aarch64-darwin": "sha256-jL4tO+EHSmUF+gQGEaLzAbTxxjkL8OyhTk13vsbomgM=",
+    "x86_64-darwin": "sha256-bsa7IpS3GaxagcigTa0yqZTkf4e/nbcTQ9aZeb+5eHQ="
   }
 }

From 76c91c6e331a4f9027e3250e485629c79fbcb880 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:04:02 -0400
Subject: [PATCH 270/378] test: migrate mcp oauth browser tests (#27345)

---
 .../opencode/test/mcp/oauth-browser.test.ts   | 251 ++++++++----------
 1 file changed, 108 insertions(+), 143 deletions(-)

diff --git a/packages/opencode/test/mcp/oauth-browser.test.ts b/packages/opencode/test/mcp/oauth-browser.test.ts
index 20cb90a18..8c8c6ca3f 100644
--- a/packages/opencode/test/mcp/oauth-browser.test.ts
+++ b/packages/opencode/test/mcp/oauth-browser.test.ts
@@ -1,15 +1,19 @@
-import { test, expect, mock, beforeEach } from "bun:test"
+import { expect, mock, beforeEach } from "bun:test"
 import { EventEmitter } from "events"
-import { Effect } from "effect"
+import { Deferred, Effect, Layer, Option } from "effect"
+import type { Duration } from "effect"
+import { testEffect } from "../lib/effect"
 import type { MCP as MCPNS } from "../../src/mcp/index"
 
 // Track open() calls and control failure behavior
 let openShouldFail = false
 let openCalledWith: string | undefined
+let openDeferred: Deferred.Deferred | undefined
 
 void mock.module("open", () => ({
   default: async (url: string) => {
     openCalledWith = url
+    if (openDeferred) Effect.runSync(Deferred.succeed(openDeferred, url).pipe(Effect.ignore))
 
     // Return a mock subprocess that emits an error if openShouldFail is true
     const subprocess = new EventEmitter()
@@ -97,173 +101,134 @@ void mock.module("@modelcontextprotocol/sdk/client/auth.js", () => ({
 beforeEach(() => {
   openShouldFail = false
   openCalledWith = undefined
+  openDeferred = undefined
   transportCalls.length = 0
 })
 
 // Import modules after mocking
 const { MCP } = await import("../../src/mcp/index")
-const { AppRuntime } = await import("../../src/effect/app-runtime")
 const { Bus } = await import("../../src/bus")
+const { Config } = await import("../../src/config/config")
+const { McpAuth } = await import("../../src/mcp/auth")
 const { McpOAuthCallback } = await import("../../src/mcp/oauth-callback")
-const { Instance } = await import("../../src/project/instance")
-const { WithInstance } = await import("../../src/project/with-instance")
-const { tmpdir } = await import("../fixture/fixture")
+const { AppFileSystem } = await import("@opencode-ai/core/filesystem")
+const { CrossSpawnSpawner } = await import("@opencode-ai/core/cross-spawn-spawner")
+const mcpTest = testEffect(
+  MCP.layer.pipe(
+    Layer.provide(McpAuth.defaultLayer),
+    Layer.provideMerge(Bus.layer),
+    Layer.provide(Config.defaultLayer),
+    Layer.provide(CrossSpawnSpawner.defaultLayer),
+    Layer.provide(AppFileSystem.defaultLayer),
+  ),
+)
 const service = MCP.Service as unknown as Effect.Effect
 
-test("BrowserOpenFailed event is published when open() throws", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      await Bun.write(
-        `${dir}/opencode.json`,
-        JSON.stringify({
-          $schema: "https://opencode.ai/config.json",
-          mcp: {
-            "test-oauth-server": {
-              type: "remote",
-              url: "https://example.com/mcp",
-            },
-          },
-        }),
-      )
+const config = (name: string) => ({
+  mcp: {
+    [name]: {
+      type: "remote" as const,
+      url: "https://example.com/mcp",
     },
-  })
-
-  await WithInstance.provide({
-    directory: tmp.path,
-    fn: async () => {
-      openShouldFail = true
-
-      const events: Array<{ mcpName: string; url: string }> = []
-      const unsubscribe = Bus.subscribe(MCP.BrowserOpenFailed, (evt) => {
-        events.push(evt.properties)
-      })
-
-      // Run authenticate with a timeout to avoid waiting forever for the callback
-      // Attach a handler immediately so callback shutdown rejections
-      // don't show up as unhandled between tests.
-      const authPromise = AppRuntime.runPromise(
-        Effect.gen(function* () {
-          const mcp = yield* service
-          return yield* mcp.authenticate("test-oauth-server")
-        }),
-      ).catch(() => undefined)
-
-      // Config.get() can be slow in tests, so give it plenty of time.
-      await new Promise((resolve) => setTimeout(resolve, 2_000))
-
-      // Stop the callback server and cancel any pending auth
-      await McpOAuthCallback.stop()
-
-      await authPromise
+  },
+})
 
-      unsubscribe()
+const withCallbackStop = Effect.addFinalizer(() => Effect.promise(() => McpOAuthCallback.stop()).pipe(Effect.ignore))
+
+const awaitWithTimeout = (
+  self: Effect.Effect,
+  message: string,
+  duration: Duration.Input = "5 seconds",
+) =>
+  self.pipe(
+    Effect.timeoutOrElse({
+      duration,
+      orElse: () => Effect.fail(new Error(message)),
+    }),
+  )
+
+const trackBrowserOpen = Effect.gen(function* () {
+  const opened = yield* Deferred.make()
+  openDeferred = opened
+  yield* Effect.addFinalizer(() => Effect.sync(() => (openDeferred = undefined)))
+  return opened
+})
 
-      // Verify the BrowserOpenFailed event was published
-      expect(events.length).toBe(1)
-      expect(events[0].mcpName).toBe("test-oauth-server")
-      expect(events[0].url).toContain("https://")
-    },
+const trackBrowserOpenFailed = Effect.gen(function* () {
+  const bus = yield* Bus.Service
+  const event = yield* Deferred.make<{ mcpName: string; url: string }>()
+  const unsubscribe = yield* bus.subscribeCallback(MCP.BrowserOpenFailed, (evt) => {
+    Effect.runSync(Deferred.succeed(event, evt.properties).pipe(Effect.ignore))
   })
+  yield* Effect.addFinalizer(() => Effect.sync(unsubscribe))
+  return event
 })
 
-test("BrowserOpenFailed event is NOT published when open() succeeds", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      await Bun.write(
-        `${dir}/opencode.json`,
-        JSON.stringify({
-          $schema: "https://opencode.ai/config.json",
-          mcp: {
-            "test-oauth-server-2": {
-              type: "remote",
-              url: "https://example.com/mcp",
-            },
-          },
-        }),
-      )
-    },
+const authenticateScoped = (name: string) =>
+  Effect.gen(function* () {
+    const mcp = yield* service
+    yield* mcp.authenticate(name).pipe(Effect.ignore, Effect.catchCause(() => Effect.void), Effect.forkScoped)
   })
 
-  await WithInstance.provide({
-    directory: tmp.path,
-    fn: async () => {
-      openShouldFail = false
-
-      const events: Array<{ mcpName: string; url: string }> = []
-      const unsubscribe = Bus.subscribe(MCP.BrowserOpenFailed, (evt) => {
-        events.push(evt.properties)
-      })
+mcpTest.instance(
+  "BrowserOpenFailed event is published when open() throws",
+  () =>
+    Effect.gen(function* () {
+      yield* withCallbackStop
+      openShouldFail = true
 
-      // Run authenticate with a timeout to avoid waiting forever for the callback
-      const authPromise = AppRuntime.runPromise(
-        Effect.gen(function* () {
-          const mcp = yield* service
-          return yield* mcp.authenticate("test-oauth-server-2")
-        }),
-      ).catch(() => undefined)
+      const event = yield* trackBrowserOpenFailed
+      yield* authenticateScoped("test-oauth-server")
 
-      // Config.get() can be slow in tests; also covers the ~500ms open() error-detection window.
-      await new Promise((resolve) => setTimeout(resolve, 2_000))
+      const failure = yield* awaitWithTimeout(
+        Deferred.await(event),
+        "Timed out waiting for BrowserOpenFailed event",
+      )
 
-      // Stop the callback server and cancel any pending auth
-      await McpOAuthCallback.stop()
+      expect(failure.mcpName).toBe("test-oauth-server")
+      expect(failure.url).toContain("https://")
+    }),
+  { config: config("test-oauth-server") },
+)
+
+mcpTest.instance(
+  "BrowserOpenFailed event is NOT published when open() succeeds",
+  () =>
+    Effect.gen(function* () {
+      yield* withCallbackStop
+      openShouldFail = false
 
-      await authPromise
+      const opened = yield* trackBrowserOpen
+      const event = yield* trackBrowserOpenFailed
+      yield* authenticateScoped("test-oauth-server-2")
 
-      unsubscribe()
+      yield* awaitWithTimeout(Deferred.await(opened), "Timed out waiting for open()")
+      const failure = yield* Deferred.await(event).pipe(Effect.timeoutOption("700 millis"))
 
-      // Verify NO BrowserOpenFailed event was published
-      expect(events.length).toBe(0)
-      // Verify open() was still called
+      expect(failure).toEqual(Option.none())
       expect(openCalledWith).toBeDefined()
-    },
-  })
-})
-
-test("open() is called with the authorization URL", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      await Bun.write(
-        `${dir}/opencode.json`,
-        JSON.stringify({
-          $schema: "https://opencode.ai/config.json",
-          mcp: {
-            "test-oauth-server-3": {
-              type: "remote",
-              url: "https://example.com/mcp",
-            },
-          },
-        }),
-      )
-    },
-  })
-
-  await WithInstance.provide({
-    directory: tmp.path,
-    fn: async () => {
+    }),
+  { config: config("test-oauth-server-2") },
+)
+
+mcpTest.instance(
+  "open() is called with the authorization URL",
+  () =>
+    Effect.gen(function* () {
+      yield* withCallbackStop
       openShouldFail = false
       openCalledWith = undefined
 
-      // Run authenticate with a timeout to avoid waiting forever for the callback
-      const authPromise = AppRuntime.runPromise(
-        Effect.gen(function* () {
-          const mcp = yield* service
-          return yield* mcp.authenticate("test-oauth-server-3")
-        }),
-      ).catch(() => undefined)
-
-      // Config.get() can be slow in tests; also covers the ~500ms open() error-detection window.
-      await new Promise((resolve) => setTimeout(resolve, 2_000))
-
-      // Stop the callback server and cancel any pending auth
-      await McpOAuthCallback.stop()
+      const opened = yield* trackBrowserOpen
+      const event = yield* trackBrowserOpenFailed
+      yield* authenticateScoped("test-oauth-server-3")
 
-      await authPromise
+      const url = yield* awaitWithTimeout(Deferred.await(opened), "Timed out waiting for open()")
+      const failure = yield* Deferred.await(event).pipe(Effect.timeoutOption("700 millis"))
 
-      // Verify open was called with a URL
-      expect(openCalledWith).toBeDefined()
-      expect(typeof openCalledWith).toBe("string")
-      expect(openCalledWith!).toContain("https://")
-    },
-  })
-})
+      expect(failure).toEqual(Option.none())
+      expect(typeof url).toBe("string")
+      expect(url).toContain("https://")
+    }),
+  { config: config("test-oauth-server-3") },
+)

From 02b8b0ff930f67d7aa128766995c6570ac63add5 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:04:30 -0400
Subject: [PATCH 271/378] test: migrate file watcher test to Effect (#27346)

---
 packages/opencode/test/file/watcher.test.ts | 289 ++++++++++----------
 1 file changed, 148 insertions(+), 141 deletions(-)

diff --git a/packages/opencode/test/file/watcher.test.ts b/packages/opencode/test/file/watcher.test.ts
index 7e47c5135..1da896cc1 100644
--- a/packages/opencode/test/file/watcher.test.ts
+++ b/packages/opencode/test/file/watcher.test.ts
@@ -1,15 +1,13 @@
-import { $ } from "bun"
-import { afterEach, describe, expect, test } from "bun:test"
-import fs from "fs/promises"
+import { describe, expect } from "bun:test"
 import path from "path"
-import { ConfigProvider, Deferred, Effect, Layer, ManagedRuntime, Option } from "effect"
-import { disposeAllInstances, tmpdir } from "../fixture/fixture"
-import { Bus } from "../../src/bus"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
+import { ConfigProvider, Deferred, Effect, Layer, Option } from "effect"
+import { TestInstance, provideInstance } from "../fixture/fixture"
+import { testEffect } from "../lib/effect"
+import { GlobalBus, type GlobalEvent } from "../../src/bus/global"
 import { Config } from "@/config/config"
 import { FileWatcher } from "../../src/file/watcher"
 import { Git } from "../../src/git"
-import { Instance } from "../../src/project/instance"
-import { WithInstance } from "../../src/project/with-instance"
 
 // Native @parcel/watcher bindings aren't reliably available in CI (missing on Linux, flaky on Windows)
 const describeWatcher = FileWatcher.hasNativeBinding() && !process.env.CI ? describe : describe.skip
@@ -25,43 +23,43 @@ const watcherConfigLayer = ConfigProvider.layer(
   }),
 )
 
+const watcherLayer = FileWatcher.layer.pipe(
+  Layer.provide(Config.defaultLayer),
+  Layer.provide(Git.defaultLayer),
+  Layer.provide(watcherConfigLayer),
+)
+
+const it = testEffect(Layer.mergeAll(AppFileSystem.defaultLayer, Git.defaultLayer))
+
 type WatcherEvent = { file: string; event: "add" | "change" | "unlink" }
 
 /** Run `body` with a live FileWatcher service. */
-function withWatcher(directory: string, body: Effect.Effect) {
-  return WithInstance.provide({
-    directory,
-    fn: async () => {
-      const layer: Layer.Layer = FileWatcher.layer.pipe(
-        Layer.provide(Config.defaultLayer),
-        Layer.provide(Git.defaultLayer),
-        Layer.provide(watcherConfigLayer),
-      )
-      const rt = ManagedRuntime.make(layer)
-      try {
-        await rt.runPromise(FileWatcher.Service.use((s) => s.init()))
-        await Effect.runPromise(ready(directory))
-        await Effect.runPromise(body)
-      } finally {
-        await rt.dispose()
-      }
-    },
-  })
+function withWatcher(directory: string, body: Effect.Effect) {
+  return Effect.gen(function* () {
+    const watcher = yield* FileWatcher.Service
+    yield* watcher.init()
+    yield* ready(directory)
+    return yield* body
+  }).pipe(Effect.provide(watcherLayer), provideInstance(directory), Effect.scoped)
 }
 
 function listen(directory: string, check: (evt: WatcherEvent) => boolean, hit: (evt: WatcherEvent) => void) {
   let done = false
 
-  const unsub = Bus.subscribe(FileWatcher.Event.Updated, (evt) => {
+  const on = (evt: GlobalEvent) => {
     if (done) return
-    if (!check(evt.properties)) return
-    hit(evt.properties)
-  })
+    if (evt.directory !== directory) return
+    if (evt.payload.type !== FileWatcher.Event.Updated.type) return
+    if (!check(evt.payload.properties)) return
+    hit(evt.payload.properties)
+  }
+
+  GlobalBus.on("event", on)
 
   return () => {
     if (done) return
     done = true
-    unsub()
+    GlobalBus.off("event", on)
   }
 }
 
@@ -72,7 +70,7 @@ function wait(directory: string, check: (evt: WatcherEvent) => boolean) {
       let off = () => {}
       off = listen(directory, check, (evt) => {
         off()
-        Deferred.doneUnsafe(deferred, Effect.succeed(evt))
+        Effect.runFork(Deferred.succeed(deferred, evt))
       })
       return off
     })
@@ -86,7 +84,12 @@ function nextUpdate(directory: string, check: (evt: WatcherEvent) => boolean,
     ({ deferred }) =>
       Effect.gen(function* () {
         yield* trigger
-        return yield* Deferred.await(deferred).pipe(Effect.timeout("5 seconds"))
+        return yield* Deferred.await(deferred).pipe(
+          Effect.timeoutOrElse({
+            duration: "5 seconds",
+            orElse: () => Effect.fail(new Error("timed out waiting for file watcher update")),
+          }),
+        )
       }),
     ({ cleanup }) => Effect.sync(cleanup),
   )
@@ -104,7 +107,11 @@ function noUpdate(
     ({ deferred }) =>
       Effect.gen(function* () {
         yield* trigger
-        expect(yield* Deferred.await(deferred).pipe(Effect.timeoutOption(`${ms} millis`))).toEqual(Option.none())
+        const result = yield* Deferred.await(deferred).pipe(
+          Effect.map((evt) => Option.some(evt)),
+          Effect.timeoutOrElse({ duration: `${ms} millis`, orElse: () => Effect.succeed(Option.none()) }),
+        )
+        expect(result).toEqual(Option.none())
       }),
     ({ cleanup }) => Effect.sync(cleanup),
   )
@@ -115,29 +122,25 @@ function ready(directory: string) {
   const head = path.join(directory, ".git", "HEAD")
 
   return Effect.gen(function* () {
+    const fs = yield* AppFileSystem.Service
+    const git = yield* Git.Service
+
     yield* nextUpdate(
       directory,
       (evt) => evt.file === file && evt.event === "add",
-      Effect.promise(() => fs.writeFile(file, "ready")),
-    ).pipe(Effect.ensuring(Effect.promise(() => fs.rm(file, { force: true }).catch(() => undefined))), Effect.asVoid)
+      fs.writeFileString(file, "ready"),
+    ).pipe(Effect.ensuring(fs.remove(file, { force: true }).pipe(Effect.ignore)), Effect.asVoid)
 
-    const git = yield* Effect.promise(() =>
-      fs
-        .stat(head)
-        .then(() => true)
-        .catch(() => false),
-    )
-    if (!git) return
+    if (!(yield* fs.existsSafe(head))) return
 
     const branch = `watch-${Math.random().toString(36).slice(2)}`
-    const hash = yield* Effect.promise(() => $`git rev-parse HEAD`.cwd(directory).quiet().text())
+    const hash = (yield* git.run(["rev-parse", "HEAD"], { cwd: directory })).text()
     yield* nextUpdate(
       directory,
       (evt) => evt.file === head && evt.event !== "unlink",
-      Effect.promise(async () => {
-        await fs.writeFile(path.join(directory, ".git", "refs", "heads", branch), hash.trim() + "\n")
-        await fs.writeFile(head, `ref: refs/heads/${branch}\n`)
-      }),
+      fs
+        .writeFileString(path.join(directory, ".git", "refs", "heads", branch), hash.trim() + "\n")
+        .pipe(Effect.andThen(fs.writeFileString(head, `ref: refs/heads/${branch}\n`))),
     ).pipe(Effect.asVoid)
   })
 }
@@ -147,104 +150,108 @@ function ready(directory: string) {
 // ---------------------------------------------------------------------------
 
 describeWatcher("FileWatcher", () => {
-  afterEach(async () => {
-    await disposeAllInstances()
-  })
+  it.instance("publishes root create, update, and delete events", () =>
+    Effect.gen(function* () {
+      const test = yield* TestInstance
+      const fs = yield* AppFileSystem.Service
+      const file = path.join(test.directory, "watch.txt")
+      const cases = [
+        { event: "add" as const, trigger: fs.writeFileString(file, "a") },
+        { event: "change" as const, trigger: fs.writeFileString(file, "b") },
+        { event: "unlink" as const, trigger: fs.remove(file) },
+      ]
 
-  test("publishes root create, update, and delete events", async () => {
-    await using tmp = await tmpdir({ git: true })
-    const file = path.join(tmp.path, "watch.txt")
-    const dir = tmp.path
-    const cases = [
-      { event: "add" as const, trigger: Effect.promise(() => fs.writeFile(file, "a")) },
-      { event: "change" as const, trigger: Effect.promise(() => fs.writeFile(file, "b")) },
-      { event: "unlink" as const, trigger: Effect.promise(() => fs.unlink(file)) },
-    ]
-
-    await withWatcher(
-      dir,
-      Effect.forEach(cases, ({ event, trigger }) =>
-        nextUpdate(dir, (evt) => evt.file === file && evt.event === event, trigger).pipe(
-          Effect.tap((evt) => Effect.sync(() => expect(evt).toEqual({ file, event }))),
+      yield* withWatcher(
+        test.directory,
+        Effect.forEach(cases, ({ event, trigger }) =>
+          nextUpdate(test.directory, (evt) => evt.file === file && evt.event === event, trigger).pipe(
+            Effect.tap((evt) => Effect.sync(() => expect(evt).toEqual({ file, event }))),
+          ),
         ),
-      ),
-    )
-  })
+      )
+    }),
+    { git: true },
+  )
 
-  test("watches non-git roots", async () => {
-    await using tmp = await tmpdir()
-    const file = path.join(tmp.path, "plain.txt")
-    const dir = tmp.path
-
-    await withWatcher(
-      dir,
-      nextUpdate(
-        dir,
-        (e) => e.file === file && e.event === "add",
-        Effect.promise(() => fs.writeFile(file, "plain")),
-      ).pipe(Effect.tap((evt) => Effect.sync(() => expect(evt).toEqual({ file, event: "add" })))),
-    )
-  })
+  it.instance("watches non-git roots", () =>
+    Effect.gen(function* () {
+      const test = yield* TestInstance
+      const fs = yield* AppFileSystem.Service
+      const file = path.join(test.directory, "plain.txt")
 
-  test("cleanup stops publishing events", async () => {
-    await using tmp = await tmpdir({ git: true })
-    const file = path.join(tmp.path, "after-dispose.txt")
-
-    // Start and immediately stop the watcher (withWatcher disposes on exit)
-    await withWatcher(tmp.path, Effect.void)
-
-    // Now write a file — no watcher should be listening
-    await WithInstance.provide({
-      directory: tmp.path,
-      fn: () =>
-        Effect.runPromise(
-          noUpdate(
-            tmp.path,
-            (e) => e.file === file,
-            Effect.promise(() => fs.writeFile(file, "gone")),
-          ),
+      yield* withWatcher(
+        test.directory,
+        nextUpdate(
+          test.directory,
+          (e) => e.file === file && e.event === "add",
+          fs.writeFileString(file, "plain"),
+        ).pipe(Effect.tap((evt) => Effect.sync(() => expect(evt).toEqual({ file, event: "add" })))),
+      )
+    }),
+  )
+
+  it.instance("cleanup stops publishing events", () =>
+    Effect.gen(function* () {
+      const test = yield* TestInstance
+      const fs = yield* AppFileSystem.Service
+      const file = path.join(test.directory, "after-dispose.txt")
+
+      // Start and immediately stop the watcher (withWatcher disposes on exit).
+      yield* withWatcher(test.directory, Effect.void)
+
+      // Now write a file - no watcher should be listening.
+      yield* noUpdate(test.directory, (e) => e.file === file, fs.writeFileString(file, "gone")).pipe(
+        provideInstance(test.directory),
+      )
+    }),
+    { git: true },
+  )
+
+  it.instance("ignores .git/index changes", () =>
+    Effect.gen(function* () {
+      const test = yield* TestInstance
+      const fs = yield* AppFileSystem.Service
+      const git = yield* Git.Service
+      const gitIndex = path.join(test.directory, ".git", "index")
+      const edit = path.join(test.directory, "tracked.txt")
+
+      yield* withWatcher(
+        test.directory,
+        noUpdate(
+          test.directory,
+          (e) => e.file === gitIndex,
+          fs.writeFileString(edit, "a").pipe(Effect.andThen(git.run(["add", "."], { cwd: test.directory }))),
         ),
-    })
-  })
+      )
+    }),
+    { git: true },
+  )
 
-  test("ignores .git/index changes", async () => {
-    await using tmp = await tmpdir({ git: true })
-    const gitIndex = path.join(tmp.path, ".git", "index")
-    const edit = path.join(tmp.path, "tracked.txt")
-
-    await withWatcher(
-      tmp.path,
-      noUpdate(
-        tmp.path,
-        (e) => e.file === gitIndex,
-        Effect.promise(async () => {
-          await fs.writeFile(edit, "a")
-          await $`git add .`.cwd(tmp.path).quiet().nothrow()
-        }),
-      ),
-    )
-  })
+  it.instance("publishes .git/HEAD events", () =>
+    Effect.gen(function* () {
+      const test = yield* TestInstance
+      const fs = yield* AppFileSystem.Service
+      const git = yield* Git.Service
+      const head = path.join(test.directory, ".git", "HEAD")
+      const branch = `watch-${Math.random().toString(36).slice(2)}`
+      yield* git.run(["branch", branch], { cwd: test.directory })
 
-  test("publishes .git/HEAD events", async () => {
-    await using tmp = await tmpdir({ git: true })
-    const head = path.join(tmp.path, ".git", "HEAD")
-    const branch = `watch-${Math.random().toString(36).slice(2)}`
-    await $`git branch ${branch}`.cwd(tmp.path).quiet()
-
-    await withWatcher(
-      tmp.path,
-      nextUpdate(
-        tmp.path,
-        (evt) => evt.file === head && evt.event !== "unlink",
-        Effect.promise(() => fs.writeFile(head, `ref: refs/heads/${branch}\n`)),
-      ).pipe(
-        Effect.tap((evt) =>
-          Effect.sync(() => {
-            expect(evt.file).toBe(head)
-            expect(["add", "change"]).toContain(evt.event)
-          }),
+      yield* withWatcher(
+        test.directory,
+        nextUpdate(
+          test.directory,
+          (evt) => evt.file === head && evt.event !== "unlink",
+          fs.writeFileString(head, `ref: refs/heads/${branch}\n`),
+        ).pipe(
+          Effect.tap((evt) =>
+            Effect.sync(() => {
+              expect(evt.file).toBe(head)
+              expect(["add", "change"]).toContain(evt.event)
+            }),
+          ),
         ),
-      ),
-    )
-  })
+      )
+    }),
+    { git: true },
+  )
 })

From 50dccac915e7d4a216238d085146a0745716f120 Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]" 
Date: Wed, 13 May 2026 15:06:14 +0000
Subject: [PATCH 272/378] chore: generate

---
 packages/opencode/test/file/watcher.test.ts   | 150 +++++++++---------
 .../opencode/test/mcp/oauth-browser.test.ts   |  11 +-
 2 files changed, 84 insertions(+), 77 deletions(-)

diff --git a/packages/opencode/test/file/watcher.test.ts b/packages/opencode/test/file/watcher.test.ts
index 1da896cc1..6276e58f2 100644
--- a/packages/opencode/test/file/watcher.test.ts
+++ b/packages/opencode/test/file/watcher.test.ts
@@ -150,26 +150,28 @@ function ready(directory: string) {
 // ---------------------------------------------------------------------------
 
 describeWatcher("FileWatcher", () => {
-  it.instance("publishes root create, update, and delete events", () =>
-    Effect.gen(function* () {
-      const test = yield* TestInstance
-      const fs = yield* AppFileSystem.Service
-      const file = path.join(test.directory, "watch.txt")
-      const cases = [
-        { event: "add" as const, trigger: fs.writeFileString(file, "a") },
-        { event: "change" as const, trigger: fs.writeFileString(file, "b") },
-        { event: "unlink" as const, trigger: fs.remove(file) },
-      ]
+  it.instance(
+    "publishes root create, update, and delete events",
+    () =>
+      Effect.gen(function* () {
+        const test = yield* TestInstance
+        const fs = yield* AppFileSystem.Service
+        const file = path.join(test.directory, "watch.txt")
+        const cases = [
+          { event: "add" as const, trigger: fs.writeFileString(file, "a") },
+          { event: "change" as const, trigger: fs.writeFileString(file, "b") },
+          { event: "unlink" as const, trigger: fs.remove(file) },
+        ]
 
-      yield* withWatcher(
-        test.directory,
-        Effect.forEach(cases, ({ event, trigger }) =>
-          nextUpdate(test.directory, (evt) => evt.file === file && evt.event === event, trigger).pipe(
-            Effect.tap((evt) => Effect.sync(() => expect(evt).toEqual({ file, event }))),
+        yield* withWatcher(
+          test.directory,
+          Effect.forEach(cases, ({ event, trigger }) =>
+            nextUpdate(test.directory, (evt) => evt.file === file && evt.event === event, trigger).pipe(
+              Effect.tap((evt) => Effect.sync(() => expect(evt).toEqual({ file, event }))),
+            ),
           ),
-        ),
-      )
-    }),
+        )
+      }),
     { git: true },
   )
 
@@ -181,77 +183,81 @@ describeWatcher("FileWatcher", () => {
 
       yield* withWatcher(
         test.directory,
-        nextUpdate(
-          test.directory,
-          (e) => e.file === file && e.event === "add",
-          fs.writeFileString(file, "plain"),
-        ).pipe(Effect.tap((evt) => Effect.sync(() => expect(evt).toEqual({ file, event: "add" })))),
+        nextUpdate(test.directory, (e) => e.file === file && e.event === "add", fs.writeFileString(file, "plain")).pipe(
+          Effect.tap((evt) => Effect.sync(() => expect(evt).toEqual({ file, event: "add" }))),
+        ),
       )
     }),
   )
 
-  it.instance("cleanup stops publishing events", () =>
-    Effect.gen(function* () {
-      const test = yield* TestInstance
-      const fs = yield* AppFileSystem.Service
-      const file = path.join(test.directory, "after-dispose.txt")
+  it.instance(
+    "cleanup stops publishing events",
+    () =>
+      Effect.gen(function* () {
+        const test = yield* TestInstance
+        const fs = yield* AppFileSystem.Service
+        const file = path.join(test.directory, "after-dispose.txt")
 
-      // Start and immediately stop the watcher (withWatcher disposes on exit).
-      yield* withWatcher(test.directory, Effect.void)
+        // Start and immediately stop the watcher (withWatcher disposes on exit).
+        yield* withWatcher(test.directory, Effect.void)
 
-      // Now write a file - no watcher should be listening.
-      yield* noUpdate(test.directory, (e) => e.file === file, fs.writeFileString(file, "gone")).pipe(
-        provideInstance(test.directory),
-      )
-    }),
+        // Now write a file - no watcher should be listening.
+        yield* noUpdate(test.directory, (e) => e.file === file, fs.writeFileString(file, "gone")).pipe(
+          provideInstance(test.directory),
+        )
+      }),
     { git: true },
   )
 
-  it.instance("ignores .git/index changes", () =>
-    Effect.gen(function* () {
-      const test = yield* TestInstance
-      const fs = yield* AppFileSystem.Service
-      const git = yield* Git.Service
-      const gitIndex = path.join(test.directory, ".git", "index")
-      const edit = path.join(test.directory, "tracked.txt")
+  it.instance(
+    "ignores .git/index changes",
+    () =>
+      Effect.gen(function* () {
+        const test = yield* TestInstance
+        const fs = yield* AppFileSystem.Service
+        const git = yield* Git.Service
+        const gitIndex = path.join(test.directory, ".git", "index")
+        const edit = path.join(test.directory, "tracked.txt")
 
-      yield* withWatcher(
-        test.directory,
-        noUpdate(
+        yield* withWatcher(
           test.directory,
-          (e) => e.file === gitIndex,
-          fs.writeFileString(edit, "a").pipe(Effect.andThen(git.run(["add", "."], { cwd: test.directory }))),
-        ),
-      )
-    }),
+          noUpdate(
+            test.directory,
+            (e) => e.file === gitIndex,
+            fs.writeFileString(edit, "a").pipe(Effect.andThen(git.run(["add", "."], { cwd: test.directory }))),
+          ),
+        )
+      }),
     { git: true },
   )
 
-  it.instance("publishes .git/HEAD events", () =>
-    Effect.gen(function* () {
-      const test = yield* TestInstance
-      const fs = yield* AppFileSystem.Service
-      const git = yield* Git.Service
-      const head = path.join(test.directory, ".git", "HEAD")
-      const branch = `watch-${Math.random().toString(36).slice(2)}`
-      yield* git.run(["branch", branch], { cwd: test.directory })
+  it.instance(
+    "publishes .git/HEAD events",
+    () =>
+      Effect.gen(function* () {
+        const test = yield* TestInstance
+        const fs = yield* AppFileSystem.Service
+        const git = yield* Git.Service
+        const head = path.join(test.directory, ".git", "HEAD")
+        const branch = `watch-${Math.random().toString(36).slice(2)}`
+        yield* git.run(["branch", branch], { cwd: test.directory })
 
-      yield* withWatcher(
-        test.directory,
-        nextUpdate(
+        yield* withWatcher(
           test.directory,
-          (evt) => evt.file === head && evt.event !== "unlink",
-          fs.writeFileString(head, `ref: refs/heads/${branch}\n`),
-        ).pipe(
-          Effect.tap((evt) =>
-            Effect.sync(() => {
-              expect(evt.file).toBe(head)
-              expect(["add", "change"]).toContain(evt.event)
-            }),
+          nextUpdate(
+            test.directory,
+            (evt) => evt.file === head && evt.event !== "unlink",
+            fs.writeFileString(head, `ref: refs/heads/${branch}\n`),
+          ).pipe(
+            Effect.tap((evt) =>
+              Effect.sync(() => {
+                expect(evt.file).toBe(head)
+                expect(["add", "change"]).toContain(evt.event)
+              }),
+            ),
           ),
-        ),
-      )
-    }),
+        )
+      }),
     { git: true },
   )
 })
diff --git a/packages/opencode/test/mcp/oauth-browser.test.ts b/packages/opencode/test/mcp/oauth-browser.test.ts
index 8c8c6ca3f..f6222de43 100644
--- a/packages/opencode/test/mcp/oauth-browser.test.ts
+++ b/packages/opencode/test/mcp/oauth-browser.test.ts
@@ -167,7 +167,11 @@ const trackBrowserOpenFailed = Effect.gen(function* () {
 const authenticateScoped = (name: string) =>
   Effect.gen(function* () {
     const mcp = yield* service
-    yield* mcp.authenticate(name).pipe(Effect.ignore, Effect.catchCause(() => Effect.void), Effect.forkScoped)
+    yield* mcp.authenticate(name).pipe(
+      Effect.ignore,
+      Effect.catchCause(() => Effect.void),
+      Effect.forkScoped,
+    )
   })
 
 mcpTest.instance(
@@ -180,10 +184,7 @@ mcpTest.instance(
       const event = yield* trackBrowserOpenFailed
       yield* authenticateScoped("test-oauth-server")
 
-      const failure = yield* awaitWithTimeout(
-        Deferred.await(event),
-        "Timed out waiting for BrowserOpenFailed event",
-      )
+      const failure = yield* awaitWithTimeout(Deferred.await(event), "Timed out waiting for BrowserOpenFailed event")
 
       expect(failure.mcpName).toBe("test-oauth-server")
       expect(failure.url).toContain("https://")

From 650f67a05aad178ae3f18903fca6b3bf6d8d101d Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:08:35 -0400
Subject: [PATCH 273/378] chore: delete unused util/lock module (#27223)

---
 packages/opencode/src/util/lock.ts       | 98 ------------------------
 packages/opencode/test/util/lock.test.ts | 72 -----------------
 2 files changed, 170 deletions(-)
 delete mode 100644 packages/opencode/src/util/lock.ts
 delete mode 100644 packages/opencode/test/util/lock.test.ts

diff --git a/packages/opencode/src/util/lock.ts b/packages/opencode/src/util/lock.ts
deleted file mode 100644
index 15635996e..000000000
--- a/packages/opencode/src/util/lock.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-const locks = new Map<
-  string,
-  {
-    readers: number
-    writer: boolean
-    waitingReaders: (() => void)[]
-    waitingWriters: (() => void)[]
-  }
->()
-
-function get(key: string) {
-  if (!locks.has(key)) {
-    locks.set(key, {
-      readers: 0,
-      writer: false,
-      waitingReaders: [],
-      waitingWriters: [],
-    })
-  }
-  return locks.get(key)!
-}
-
-function process(key: string) {
-  const lock = locks.get(key)
-  if (!lock || lock.writer || lock.readers > 0) return
-
-  // Prioritize writers to prevent starvation
-  if (lock.waitingWriters.length > 0) {
-    const nextWriter = lock.waitingWriters.shift()!
-    nextWriter()
-    return
-  }
-
-  // Wake up all waiting readers
-  while (lock.waitingReaders.length > 0) {
-    const nextReader = lock.waitingReaders.shift()!
-    nextReader()
-  }
-
-  // Clean up empty locks
-  if (lock.readers === 0 && !lock.writer && lock.waitingReaders.length === 0 && lock.waitingWriters.length === 0) {
-    locks.delete(key)
-  }
-}
-
-export async function read(key: string): Promise {
-  const lock = get(key)
-
-  return new Promise((resolve) => {
-    if (!lock.writer && lock.waitingWriters.length === 0) {
-      lock.readers++
-      resolve({
-        [Symbol.dispose]: () => {
-          lock.readers--
-          process(key)
-        },
-      })
-    } else {
-      lock.waitingReaders.push(() => {
-        lock.readers++
-        resolve({
-          [Symbol.dispose]: () => {
-            lock.readers--
-            process(key)
-          },
-        })
-      })
-    }
-  })
-}
-
-export async function write(key: string): Promise {
-  const lock = get(key)
-
-  return new Promise((resolve) => {
-    if (!lock.writer && lock.readers === 0) {
-      lock.writer = true
-      resolve({
-        [Symbol.dispose]: () => {
-          lock.writer = false
-          process(key)
-        },
-      })
-    } else {
-      lock.waitingWriters.push(() => {
-        lock.writer = true
-        resolve({
-          [Symbol.dispose]: () => {
-            lock.writer = false
-            process(key)
-          },
-        })
-      })
-    }
-  })
-}
-
-export * as Lock from "./lock"
diff --git a/packages/opencode/test/util/lock.test.ts b/packages/opencode/test/util/lock.test.ts
deleted file mode 100644
index 79fbb5831..000000000
--- a/packages/opencode/test/util/lock.test.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { describe, expect, test } from "bun:test"
-import { Lock } from "@/util/lock"
-
-function tick() {
-  return new Promise((r) => queueMicrotask(r))
-}
-
-async function flush(n = 5) {
-  for (let i = 0; i < n; i++) await tick()
-}
-
-describe("util.lock", () => {
-  test("writer exclusivity: blocks reads and other writes while held", async () => {
-    const key = "lock:" + Math.random().toString(36).slice(2)
-
-    const state = {
-      writer2: false,
-      reader: false,
-      writers: 0,
-    }
-
-    // Acquire writer1
-    using writer1 = await Lock.write(key)
-    state.writers++
-    expect(state.writers).toBe(1)
-
-    // Start writer2 candidate (should block)
-    const writer2Task = (async () => {
-      const w = await Lock.write(key)
-      state.writers++
-      expect(state.writers).toBe(1)
-      state.writer2 = true
-      // Hold for a tick so reader cannot slip in
-      await tick()
-      return w
-    })()
-
-    // Start reader candidate (should block)
-    const readerTask = (async () => {
-      const r = await Lock.read(key)
-      state.reader = true
-      return r
-    })()
-
-    // Flush microtasks and assert neither acquired
-    await flush()
-    expect(state.writer2).toBe(false)
-    expect(state.reader).toBe(false)
-
-    // Release writer1
-    writer1[Symbol.dispose]()
-    state.writers--
-
-    // writer2 should acquire next
-    const writer2 = await writer2Task
-    expect(state.writer2).toBe(true)
-
-    // Reader still blocked while writer2 held
-    await flush()
-    expect(state.reader).toBe(false)
-
-    // Release writer2
-    writer2[Symbol.dispose]()
-    state.writers--
-
-    // Reader should now acquire
-    const reader = await readerTask
-    expect(state.reader).toBe(true)
-
-    reader[Symbol.dispose]()
-  })
-})

From ca723f1cbc6fc4244ae57e61e9de8c4e37380ed4 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:10:23 -0400
Subject: [PATCH 274/378] effect(core): add stdin option to AppProcess.run;
 migrate snapshot+clipboard (#27224)

---
 packages/core/src/process.ts                  |  29 ++++-
 packages/core/test/process/process.test.ts    | 105 +++++++++++++++---
 .../src/cli/cmd/tui/util/clipboard.ts         |  60 +++-------
 packages/opencode/src/snapshot/index.ts       |  74 +++---------
 4 files changed, 150 insertions(+), 118 deletions(-)

diff --git a/packages/core/src/process.ts b/packages/core/src/process.ts
index 2da8eb834..76ea9cf3f 100644
--- a/packages/core/src/process.ts
+++ b/packages/core/src/process.ts
@@ -16,6 +16,7 @@ export interface RunOptions {
   readonly maxErrorBytes?: number
   readonly signal?: AbortSignal
   readonly timeout?: Duration.Input
+  readonly stdin?: string | Uint8Array | Stream.Stream
 }
 
 export interface RunStreamOptions {
@@ -96,6 +97,15 @@ const waitForAbort = (signal: AbortSignal) =>
     return Effect.sync(() => signal.removeEventListener("abort", onabort))
   })
 
+const normalizeStdin = (
+  input: string | Uint8Array | Stream.Stream,
+): Stream.Stream =>
+  typeof input === "string"
+    ? Stream.make(new TextEncoder().encode(input))
+    : input instanceof Uint8Array
+      ? Stream.make(input)
+      : input
+
 const collectStream = (stream: Stream.Stream, maxOutputBytes: number | undefined) =>
   Stream.runFold(
     stream,
@@ -119,7 +129,7 @@ export const layer = Layer.effect(
   Effect.gen(function* () {
     const spawner = yield* ChildProcessSpawner
 
-    const run = Effect.fn("AppProcess.run")(function* (command: ChildProcess.Command, options?: RunOptions) {
+    const runCommand = (command: ChildProcess.Command, options?: RunOptions) => {
       const description = describeCommand(command)
       const collect = Effect.scoped(
         Effect.gen(function* () {
@@ -154,7 +164,22 @@ export const layer = Layer.effect(
             ),
           )
         : timed
-      return yield* aborted.pipe(Effect.catch((cause) => Effect.fail(wrapError(description, cause))))
+      return aborted.pipe(Effect.catch((cause) => Effect.fail(wrapError(description, cause))))
+    }
+
+    const run = Effect.fn("AppProcess.run")(function* (command: ChildProcess.Command, options?: RunOptions) {
+      if (options?.stdin === undefined) return yield* runCommand(command, options)
+      if (command._tag !== "StandardCommand") {
+        return yield* new AppProcessError({
+          command: describeCommand(command),
+          cause: new Error("stdin option only supports StandardCommand; received PipedCommand"),
+        })
+      }
+      const next = ChildProcess.make(command.command, command.args, {
+        ...command.options,
+        stdin: normalizeStdin(options.stdin),
+      })
+      return yield* runCommand(next, options)
     })
 
     const runStream = (
diff --git a/packages/core/test/process/process.test.ts b/packages/core/test/process/process.test.ts
index 726c3c4d8..5cc73e616 100644
--- a/packages/core/test/process/process.test.ts
+++ b/packages/core/test/process/process.test.ts
@@ -1,4 +1,6 @@
 import { describe, expect } from "bun:test"
+import { realpathSync } from "node:fs"
+import { tmpdir } from "node:os"
 import { Effect, Exit, Stream } from "effect"
 import { ChildProcess } from "effect/unstable/process"
 import { AppProcess } from "@opencode-ai/core/process"
@@ -123,6 +125,82 @@ describe("AppProcess", () => {
     )
   })
 
+  describe("run with stdin option", () => {
+    const echoStdin = "process.stdin.on('data', c => process.stdout.write(c))"
+
+    it.effect(
+      "feeds a string to stdin and returns it on stdout",
+      Effect.gen(function* () {
+        const svc = yield* AppProcess.Service
+        const result = yield* svc.run(cmd("-e", echoStdin), { stdin: "hello" })
+        expect(result.exitCode).toBe(0)
+        expect(result.stdout.toString("utf8")).toBe("hello")
+      }),
+    )
+
+    it.effect(
+      "feeds a Uint8Array to stdin",
+      Effect.gen(function* () {
+        const svc = yield* AppProcess.Service
+        const bytes = new TextEncoder().encode("bytes")
+        const result = yield* svc.run(cmd("-e", echoStdin), { stdin: bytes })
+        expect(result.exitCode).toBe(0)
+        expect(result.stdout.toString("utf8")).toBe("bytes")
+      }),
+    )
+
+    it.effect(
+      "feeds a Stream of Uint8Array chunks to stdin",
+      Effect.gen(function* () {
+        const svc = yield* AppProcess.Service
+        const enc = new TextEncoder()
+        const stream = Stream.fromIterable([enc.encode("one"), enc.encode("-two"), enc.encode("-three")])
+        const result = yield* svc.run(cmd("-e", echoStdin), { stdin: stream })
+        expect(result.exitCode).toBe(0)
+        expect(result.stdout.toString("utf8")).toBe("one-two-three")
+      }),
+    )
+
+    it.effect(
+      "completes correctly with empty input",
+      Effect.gen(function* () {
+        const svc = yield* AppProcess.Service
+        const result = yield* svc.run(cmd("-e", echoStdin), { stdin: "" })
+        expect(result.exitCode).toBe(0)
+        expect(result.stdout.toString("utf8")).toBe("")
+      }),
+    )
+
+    it.effect(
+      "carries existing Command options like env",
+      Effect.gen(function* () {
+        const svc = yield* AppProcess.Service
+        const script =
+          "process.stdout.write(process.env.FEED + ':'); process.stdin.on('data', c => process.stdout.write(c))"
+        const command = ChildProcess.make(NODE, ["-e", script], { env: { FEED: "envset" }, extendEnv: true })
+        const result = yield* svc.run(command, { stdin: "payload" })
+        expect(result.exitCode).toBe(0)
+        expect(result.stdout.toString("utf8")).toBe("envset:payload")
+      }),
+    )
+
+    it.effect(
+      "carries existing Command options like cwd",
+      Effect.gen(function* () {
+        const svc = yield* AppProcess.Service
+        const dir = realpathSync(tmpdir())
+        const script =
+          "process.stdout.write(process.cwd() + '|'); process.stdin.on('data', c => process.stdout.write(c))"
+        const command = ChildProcess.make(NODE, ["-e", script], { cwd: dir })
+        const result = yield* svc.run(command, { stdin: "ok" })
+        expect(result.exitCode).toBe(0)
+        const [cwd, stdin] = result.stdout.toString("utf8").split("|")
+        expect(realpathSync(cwd)).toBe(dir)
+        expect(stdin).toBe("ok")
+      }),
+    )
+  })
+
   describe("runStream", () => {
     it.live(
       "emits lines incrementally and ends cleanly on exit 0",
@@ -136,11 +214,17 @@ describe("AppProcess", () => {
     )
 
     it.live(
-      "fails with AppProcessError when exit not in okExitCodes",
+      "okExitCodes determines whether a non-zero exit fails the stream",
       Effect.gen(function* () {
         const svc = yield* AppProcess.Service
+        const allowed = yield* svc
+          .runStream(cmd("-e", "console.log('only'); process.exit(1)"), { okExitCodes: [0, 1] })
+          .pipe(Stream.runCollect)
+        expect(Array.from(allowed)).toEqual(["only"])
         const exit = yield* Effect.exit(
-          svc.runStream(cmd("-e", "console.log('a'); process.exit(2)"), { okExitCodes: [0] }).pipe(Stream.runCollect),
+          svc
+            .runStream(cmd("-e", "console.log('a'); process.exit(2)"), { okExitCodes: [0, 1] })
+            .pipe(Stream.runCollect),
         )
         expect(Exit.isFailure(exit)).toBe(true)
         if (Exit.isFailure(exit)) {
@@ -152,17 +236,6 @@ describe("AppProcess", () => {
       }),
     )
 
-    it.live(
-      "okExitCodes allowlist treats non-zero as success",
-      Effect.gen(function* () {
-        const svc = yield* AppProcess.Service
-        const result = yield* svc
-          .runStream(cmd("-e", "console.log('only'); process.exit(1)"), { okExitCodes: [0, 1] })
-          .pipe(Stream.runCollect)
-        expect(Array.from(result)).toEqual(["only"])
-      }),
-    )
-
     it.live(
       "without okExitCodes, never fails on exit code",
       Effect.gen(function* () {
@@ -177,12 +250,10 @@ describe("AppProcess", () => {
       Effect.gen(function* () {
         const svc = yield* AppProcess.Service
         const controller = new AbortController()
-        setTimeout(() => controller.abort(), 50)
+        controller.abort()
         const exit = yield* Effect.exit(
           svc
-            .runStream(cmd("-e", "setInterval(() => console.log('tick'), 100); setTimeout(() => {}, 60_000)"), {
-              signal: controller.signal,
-            })
+            .runStream(cmd("-e", "setInterval(() => {}, 60_000)"), { signal: controller.signal })
             .pipe(Stream.runCollect),
         )
         expect(Exit.isFailure(exit)).toBe(true)
diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
index 3a9996902..be3cec14c 100644
--- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
+++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
@@ -3,9 +3,21 @@ import { lazy } from "../../../../util/lazy.js"
 import { tmpdir } from "os"
 import path from "path"
 import fs from "fs/promises"
+import { Effect } from "effect"
+import { ChildProcess } from "effect/unstable/process"
+import { AppProcess } from "@opencode-ai/core/process"
 import * as Filesystem from "../../../../util/filesystem"
 import * as Process from "../../../../util/process"
 
+const writeWithStdin = (cmd: string[], text: string): Promise =>
+  Effect.runPromise(
+    AppProcess.Service.use((svc) => svc.run(ChildProcess.make(cmd[0]!, cmd.slice(1)), { stdin: text })).pipe(
+      Effect.provide(AppProcess.defaultLayer),
+      Effect.catch(() => Effect.void),
+      Effect.asVoid,
+    ),
+  ).catch(() => undefined)
+
 // Lazy load which and clipboardy to avoid expensive execa/which/isexe chain at startup
 const getWhich = lazy(async () => {
   const { which } = await import("../../../../util/which")
@@ -125,49 +137,23 @@ const getCopyMethod = lazy(async () => {
   if (os === "linux") {
     if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) {
       console.log("clipboard: using wl-copy")
-      return async (text: string) => {
-        const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
-        if (!proc.stdin) return
-        proc.stdin.write(text)
-        proc.stdin.end()
-        await proc.exited.catch(() => {})
-      }
+      return (text: string) => writeWithStdin(["wl-copy"], text)
     }
     if (which("xclip")) {
       console.log("clipboard: using xclip")
-      return async (text: string) => {
-        const proc = Process.spawn(["xclip", "-selection", "clipboard"], {
-          stdin: "pipe",
-          stdout: "ignore",
-          stderr: "ignore",
-        })
-        if (!proc.stdin) return
-        proc.stdin.write(text)
-        proc.stdin.end()
-        await proc.exited.catch(() => {})
-      }
+      return (text: string) => writeWithStdin(["xclip", "-selection", "clipboard"], text)
     }
     if (which("xsel")) {
       console.log("clipboard: using xsel")
-      return async (text: string) => {
-        const proc = Process.spawn(["xsel", "--clipboard", "--input"], {
-          stdin: "pipe",
-          stdout: "ignore",
-          stderr: "ignore",
-        })
-        if (!proc.stdin) return
-        proc.stdin.write(text)
-        proc.stdin.end()
-        await proc.exited.catch(() => {})
-      }
+      return (text: string) => writeWithStdin(["xsel", "--clipboard", "--input"], text)
     }
   }
 
   if (os === "win32") {
     console.log("clipboard: using powershell")
-    return async (text: string) => {
+    return (text: string) =>
       // Pipe via stdin to avoid PowerShell string interpolation ($env:FOO, $(), etc.)
-      const proc = Process.spawn(
+      writeWithStdin(
         [
           "powershell.exe",
           "-NonInteractive",
@@ -175,18 +161,8 @@ const getCopyMethod = lazy(async () => {
           "-Command",
           "[Console]::InputEncoding = [System.Text.Encoding]::UTF8; Set-Clipboard -Value ([Console]::In.ReadToEnd())",
         ],
-        {
-          stdin: "pipe",
-          stdout: "ignore",
-          stderr: "ignore",
-        },
+        text,
       )
-
-      if (!proc.stdin) return
-      proc.stdin.write(text)
-      proc.stdin.end()
-      await proc.exited.catch(() => {})
-    }
   }
 
   console.log("clipboard: no native support")
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index a5f080069..f974a457a 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -1,4 +1,4 @@
-import { Cause, Duration, Effect, Layer, Schedule, Schema, Semaphore, Context, Stream } from "effect"
+import { Cause, Duration, Effect, Layer, Schedule, Schema, Semaphore, Context } from "effect"
 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
 import { formatPatch, structuredPatch } from "diff"
 import path from "path"
@@ -84,48 +84,13 @@ export const layer: Layer.Layer ["--git-dir", state.gitdir, "--work-tree", state.worktree, ...cmd]
 
-          const enc = new TextEncoder()
-          const feed = (list: string[]) => Stream.make(enc.encode(list.join("\0") + "\0"))
-
-          const gitWithStdin = Effect.fnUntraced(
-            function* (
-              cmd: string[],
-              opts: { cwd?: string; env?: Record; stdin: ChildProcess.CommandInput },
-            ) {
-              // stdin-feed calls still need raw spawn — AppProcess.run does not yet
-              // expose a stdin Stream API. Tracked as future AppProcess helper.
-              const proc = ChildProcess.make("git", cmd, {
-                cwd: opts.cwd,
-                env: opts.env,
-                extendEnv: true,
-                stdin: opts.stdin,
-              })
-              const handle = yield* appProcess.spawn(proc)
-              const [text, stderr] = yield* Effect.all(
-                [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
-                { concurrency: 2 },
-              )
-              const code = yield* handle.exitCode
-              return { code, text, stderr } satisfies GitResult
-            },
-            Effect.scoped,
-            Effect.catch((err) =>
-              Effect.succeed({
-                code: ChildProcessSpawner.ExitCode(1),
-                text: "",
-                stderr: err instanceof Error ? err.message : String(err),
-              }),
-            ),
-          )
+          const feed = (list: string[]) => list.join("\0") + "\0"
 
           const git = Effect.fnUntraced(
-            function* (cmd: string[], opts?: { cwd?: string; env?: Record }) {
+            function* (cmd: string[], opts?: { cwd?: string; env?: Record; stdin?: string }) {
               const result = yield* appProcess.run(
-                ChildProcess.make("git", cmd, {
-                  cwd: opts?.cwd,
-                  env: opts?.env,
-                  extendEnv: true,
-                }),
+                ChildProcess.make("git", cmd, { cwd: opts?.cwd, env: opts?.env, extendEnv: true }),
+                { stdin: opts?.stdin },
               )
               return {
                 code: ChildProcessSpawner.ExitCode(result.exitCode),
@@ -144,7 +109,7 @@ export const layer: Layer.Layer()
-            const check = yield* gitWithStdin(
+            const check = yield* git(
               [
                 ...quote,
                 "--git-dir",
@@ -167,7 +132,7 @@ export const layer: Layer.Layer()
 
-                    // cat-file --batch is a stdin-feed call — kept on raw spawn
-                    // until AppProcess.run exposes a stdin Stream API.
-                    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* appProcess.spawn(proc)
-                    const [out, err] = yield* Effect.all(
-                      [Stream.mkUint8Array(handle.stdout), Stream.mkString(Stream.decodeText(handle.stderr))],
-                      { concurrency: 2 },
+                    const batch = yield* appProcess.run(
+                      ChildProcess.make("git", [...cfg, ...args(["cat-file", "--batch"])], {
+                        cwd: state.directory,
+                        extendEnv: true,
+                      }),
+                      { stdin: refs.map((item) => item.ref).join("\n") + "\n" },
                     )
-                    const code = yield* handle.exitCode
-                    if (code !== 0) {
+                    if (batch.exitCode !== 0) {
                       log.info("git cat-file --batch failed during snapshot diff, falling back to per-file git show", {
-                        stderr: err,
+                        stderr: batch.stderr.toString("utf8"),
                         refs: refs.length,
                       })
                       return
                     }
+                    const out = batch.stdout
 
                     const fail = (msg: string, extra?: Record) => {
                       log.info(msg, { ...extra, refs: refs.length })

From 6e25720307400a93ebf1e4f22412443cd4b38087 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:18:35 -0400
Subject: [PATCH 275/378] test(tool): use Effect sleep in edit concurrency test
 (#27349)

---
 packages/opencode/test/tool/edit.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts
index 6a1682826..3f644ed53 100644
--- a/packages/opencode/test/tool/edit.test.ts
+++ b/packages/opencode/test/tool/edit.test.ts
@@ -500,7 +500,7 @@ describe("tool.edit", () => {
               asks++
               if (asks !== 1) return
               yield* Deferred.succeed(firstAsk, undefined)
-              yield* Effect.promise(() => Bun.sleep(50))
+              yield* Effect.sleep("50 millis")
             }),
         }
 

From 6c7f35b49e66e684bd53b4c834dc3932db2cf63b Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:26:42 -0400
Subject: [PATCH 276/378] effect(format): migrate to AppProcess (#27185)

---
 packages/opencode/src/format/index.ts | 24 ++++++++++--------------
 1 file changed, 10 insertions(+), 14 deletions(-)

diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts
index b6eb9dfd0..d8365e0fa 100644
--- a/packages/opencode/src/format/index.ts
+++ b/packages/opencode/src/format/index.ts
@@ -1,6 +1,6 @@
 import { Effect, Layer, Context, Schema } from "effect"
-import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
-import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
+import { ChildProcess } from "effect/unstable/process"
+import { AppProcess } from "@opencode-ai/core/process"
 import { InstanceState } from "@/effect/instance-state"
 import path from "path"
 import { mergeDeep } from "remeda"
@@ -29,7 +29,7 @@ export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {
     const config = yield* Config.Service
-    const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
+    const appProcess = yield* AppProcess.Service
 
     const state = yield* InstanceState.make(
       Effect.fn("Format.state")(function* (ctx) {
@@ -81,8 +81,8 @@ export const layer = Layer.effect(
               log.info("running", { command: cmd })
               const replaced = cmd.map((x) => x.replace("$FILE", filepath))
               const dir = yield* InstanceState.directory
-              const code = yield* spawner
-                .spawn(
+              const result = yield* appProcess
+                .run(
                   ChildProcess.make(replaced[0]!, replaced.slice(1), {
                     cwd: dir,
                     env: item.environment,
@@ -93,21 +93,20 @@ export const layer = Layer.effect(
                   }),
                 )
                 .pipe(
-                  Effect.flatMap((handle) => handle.exitCode),
-                  Effect.scoped,
-                  Effect.catch(() =>
+                  Effect.catch((error) =>
                     Effect.sync(() => {
                       log.error("failed to format file", {
                         error: "spawn failed",
                         command: cmd,
                         ...item.environment,
                         file: filepath,
+                        cause: error.message,
                       })
-                      return ChildProcessSpawner.ExitCode(1)
+                      return undefined
                     }),
                   ),
                 )
-              if (code !== 0) {
+              if (result && result.exitCode !== 0) {
                 log.error("failed", {
                   command: cmd,
                   ...item.environment,
@@ -198,9 +197,6 @@ export const layer = Layer.effect(
   }),
 )
 
-export const defaultLayer = layer.pipe(
-  Layer.provide(Config.defaultLayer),
-  Layer.provide(CrossSpawnSpawner.defaultLayer),
-)
+export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(AppProcess.defaultLayer))
 
 export * as Format from "."

From 832aa949771530e74d85b9b751a0f679dc1069a2 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:39:35 -0400
Subject: [PATCH 277/378] effect(worktree): migrate to AppProcess.run (#27186)

---
 packages/opencode/src/worktree/index.ts | 50 ++++++++++---------------
 1 file changed, 20 insertions(+), 30 deletions(-)

diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts
index 93e56605b..8f7910208 100644
--- a/packages/opencode/src/worktree/index.ts
+++ b/packages/opencode/src/worktree/index.ts
@@ -12,12 +12,12 @@ import { errorMessage } from "../util/error"
 import { BusEvent } from "@/bus/bus-event"
 import { GlobalBus } from "@/bus/global"
 import { Git } from "@/git"
-import { Effect, Layer, Path, Schema, Scope, Context, Stream } from "effect"
-import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
+import { Effect, Layer, Path, Schema, Scope, Context } from "effect"
+import { ChildProcess } from "effect/unstable/process"
 import { NodePath } from "@effect/platform-node"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { BootstrapRuntime } from "@/effect/bootstrap-runtime"
-import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
+import { AppProcess } from "@opencode-ai/core/process"
 import { InstanceState } from "@/effect/instance-state"
 
 const log = Log.create({ service: "worktree" })
@@ -150,38 +150,35 @@ type GitResult = { code: number; text: string; stderr: string }
 export const layer: Layer.Layer<
   Service,
   never,
-  | AppFileSystem.Service
-  | Path.Path
-  | ChildProcessSpawner.ChildProcessSpawner
-  | Git.Service
-  | Project.Service
-  | InstanceStore.Service
+  AppFileSystem.Service | Path.Path | AppProcess.Service | Git.Service | Project.Service | InstanceStore.Service
 > = Layer.effect(
   Service,
   Effect.gen(function* () {
     const scope = yield* Scope.Scope
     const fs = yield* AppFileSystem.Service
     const pathSvc = yield* Path.Path
-    const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
+    const appProcess = yield* AppProcess.Service
     const gitSvc = yield* Git.Service
     const project = yield* Project.Service
     const store = yield* InstanceStore.Service
 
     const git = Effect.fnUntraced(
       function* (args: string[], opts?: { cwd?: string }) {
-        const handle = yield* spawner.spawn(
+        const result = yield* appProcess.run(
           ChildProcess.make("git", args, { cwd: opts?.cwd, extendEnv: true, stdin: "ignore" }),
         )
-        const [text, stderr] = yield* Effect.all(
-          [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
-          { concurrency: 2 },
-        )
-        const code = yield* handle.exitCode
-        return { code, text, stderr } satisfies GitResult
+        return {
+          code: result.exitCode,
+          text: result.stdout.toString("utf8"),
+          stderr: result.stderr.toString("utf8"),
+        } satisfies GitResult
       },
-      Effect.scoped,
       Effect.catch((e) =>
-        Effect.succeed({ code: 1, text: "", stderr: e instanceof Error ? e.message : String(e) } satisfies GitResult),
+        Effect.succeed({
+          code: 1,
+          text: "",
+          stderr: e instanceof Error ? e.message : String(e),
+        } satisfies GitResult),
       ),
     )
 
@@ -461,18 +458,11 @@ export const layer: Layer.Layer<
     const runStartCommand = Effect.fnUntraced(
       function* (directory: string, cmd: string) {
         const [shell, args] = process.platform === "win32" ? ["cmd", ["/c", cmd]] : ["bash", ["-lc", cmd]]
-        const handle = yield* spawner.spawn(
-          ChildProcess.make(shell, args, { cwd: directory, extendEnv: true, stdin: "ignore" }),
+        const result = yield* appProcess.run(
+          ChildProcess.make(shell, args as string[], { cwd: directory, extendEnv: true, stdin: "ignore" }),
         )
-        // Drain stdout, capture stderr for error reporting
-        const [, stderr] = yield* Effect.all(
-          [Stream.runDrain(handle.stdout), Stream.mkString(Stream.decodeText(handle.stderr))],
-          { concurrency: 2 },
-        ).pipe(Effect.orDie)
-        const code = yield* handle.exitCode
-        return { code, stderr }
+        return { code: result.exitCode, stderr: result.stderr.toString("utf8") }
       },
-      Effect.scoped,
       Effect.catch(() => Effect.succeed({ code: 1, stderr: "" })),
     )
 
@@ -620,7 +610,7 @@ export const layer: Layer.Layer<
 
 export const appLayer = layer.pipe(
   Layer.provide(Git.defaultLayer),
-  Layer.provide(CrossSpawnSpawner.defaultLayer),
+  Layer.provide(AppProcess.defaultLayer),
   Layer.provide(Project.defaultLayer),
   Layer.provide(AppFileSystem.defaultLayer),
   Layer.provide(NodePath.layer),

From e5319846add53292a258e197650d1bf75182cd40 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:43:09 -0400
Subject: [PATCH 278/378] test(server): migrate pty websocket input test
 (#27348)

---
 .../test/server/httpapi-pty-websocket.test.ts | 21 +++++++++++--------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/packages/opencode/test/server/httpapi-pty-websocket.test.ts b/packages/opencode/test/server/httpapi-pty-websocket.test.ts
index 81ee952d9..19d97ef09 100644
--- a/packages/opencode/test/server/httpapi-pty-websocket.test.ts
+++ b/packages/opencode/test/server/httpapi-pty-websocket.test.ts
@@ -1,16 +1,19 @@
-import { describe, expect, test } from "bun:test"
+import { describe, expect } from "bun:test"
 import { Effect } from "effect"
 import { handlePtyInput } from "../../src/pty/input"
+import { it } from "../lib/effect"
 
 describe("pty HttpApi websocket input", () => {
-  test("does not forward invalid binary frames to the PTY handler", async () => {
-    const messages: Array = []
-    const handler = { onMessage: (message: string | ArrayBuffer) => messages.push(message) }
+  it.effect("does not forward invalid binary frames to the PTY handler", () =>
+    Effect.gen(function* () {
+      const messages: Array = []
+      const handler = { onMessage: (message: string | ArrayBuffer) => messages.push(message) }
 
-    await Effect.runPromise(handlePtyInput(handler, "ready"))
-    await Effect.runPromise(handlePtyInput(handler, new Uint8Array([0xff, 0xfe, 0xfd])))
-    await Effect.runPromise(handlePtyInput(handler, new TextEncoder().encode("hello")))
+      yield* handlePtyInput(handler, "ready")
+      yield* handlePtyInput(handler, new Uint8Array([0xff, 0xfe, 0xfd]))
+      yield* handlePtyInput(handler, new TextEncoder().encode("hello"))
 
-    expect(messages).toEqual(["ready", "hello"])
-  })
+      expect(messages).toEqual(["ready", "hello"])
+    }),
+  )
 })

From 5cdbb7505efd09dfd588b732118e6f4c970c4a3d Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 11:54:29 -0400
Subject: [PATCH 279/378] effect(installation): migrate to AppProcess.run
 (#27188)

---
 packages/opencode/src/installation/index.ts   | 431 +++++++++---------
 .../test/installation/installation.test.ts    |   4 +-
 2 files changed, 213 insertions(+), 222 deletions(-)

diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts
index e8c434276..cc0b06e8e 100644
--- a/packages/opencode/src/installation/index.ts
+++ b/packages/opencode/src/installation/index.ts
@@ -1,8 +1,8 @@
 import { Effect, Layer, Schema, Context, Stream } from "effect"
 import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
-import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { withTransientReadRetry } from "@/util/effect-http-client"
-import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
+import { ChildProcess } from "effect/unstable/process"
+import { AppProcess } from "@opencode-ai/core/process"
 import path from "path"
 import { BusEvent } from "@/bus/bus-event"
 import { Flag } from "@opencode-ai/core/flag/flag"
@@ -85,246 +85,235 @@ export interface Interface {
 
 export class Service extends Context.Service()("@opencode/Installation") {}
 
-export const layer: Layer.Layer =
-  Layer.effect(
-    Service,
-    Effect.gen(function* () {
-      const http = yield* HttpClient.HttpClient
-      const httpOk = HttpClient.filterStatusOk(withTransientReadRetry(http))
-      const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
-
-      const text = Effect.fnUntraced(
-        function* (cmd: string[], opts?: { cwd?: string; env?: Record }) {
-          const proc = ChildProcess.make(cmd[0], cmd.slice(1), {
+export const layer: Layer.Layer = Layer.effect(
+  Service,
+  Effect.gen(function* () {
+    const http = yield* HttpClient.HttpClient
+    const httpOk = HttpClient.filterStatusOk(withTransientReadRetry(http))
+    const appProcess = yield* AppProcess.Service
+
+    const text = Effect.fnUntraced(
+      function* (cmd: string[], opts?: { cwd?: string; env?: Record }) {
+        const result = yield* appProcess.run(
+          ChildProcess.make(cmd[0], cmd.slice(1), {
             cwd: opts?.cwd,
             env: opts?.env,
             extendEnv: true,
-          })
-          const handle = yield* spawner.spawn(proc)
-          const out = yield* Stream.mkString(Stream.decodeText(handle.stdout))
-          yield* handle.exitCode
-          return out
-        },
-        Effect.scoped,
-        Effect.catch(() => Effect.succeed("")),
-      )
-
-      const run = Effect.fnUntraced(
-        function* (cmd: string[], opts?: { cwd?: string; env?: Record }) {
-          const proc = ChildProcess.make(cmd[0], cmd.slice(1), {
+          }),
+        )
+        return result.stdout.toString("utf8")
+      },
+      Effect.catch(() => Effect.succeed("")),
+    )
+
+    const run = Effect.fnUntraced(
+      function* (cmd: string[], opts?: { cwd?: string; env?: Record }) {
+        const result = yield* appProcess.run(
+          ChildProcess.make(cmd[0], cmd.slice(1), {
             cwd: opts?.cwd,
             env: opts?.env,
             extendEnv: true,
-          })
-          const handle = yield* spawner.spawn(proc)
-          const [stdout, stderr] = yield* Effect.all(
-            [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
-            { concurrency: 2 },
-          )
-          const code = yield* handle.exitCode
-          return { code, stdout, stderr }
-        },
-        Effect.scoped,
-        Effect.catch(() => Effect.succeed({ code: ChildProcessSpawner.ExitCode(1), stdout: "", stderr: "" })),
-      )
-
-      const getBrewFormula = Effect.fnUntraced(function* () {
-        const tapFormula = yield* text(["brew", "list", "--formula", "anomalyco/tap/opencode"])
-        if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode"
-        const coreFormula = yield* text(["brew", "list", "--formula", "opencode"])
-        if (coreFormula.includes("opencode")) return "opencode"
-        return "opencode"
-      })
-
-      const upgradeCurl = Effect.fnUntraced(
-        function* (target: string) {
-          const response = yield* httpOk.execute(HttpClientRequest.get("https://opencode.ai/install"))
-          const body = yield* response.text
-          const bodyBytes = new TextEncoder().encode(body)
-          const proc = ChildProcess.make("bash", [], {
-            stdin: Stream.make(bodyBytes),
-            env: { VERSION: target },
-            extendEnv: true,
-          })
-          const handle = yield* spawner.spawn(proc)
-          const [stdout, stderr] = yield* Effect.all(
-            [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
-            { concurrency: 2 },
-          )
-          const code = yield* handle.exitCode
-          return { code, stdout, stderr }
-        },
-        Effect.scoped,
-        Effect.orDie,
-      )
-
-      const result: Interface = {
-        info: Effect.fn("Installation.info")(function* () {
-          return {
-            version: InstallationVersion,
-            latest: yield* result.latest(),
-          }
-        }),
-        method: Effect.fn("Installation.method")(function* () {
-          if (process.execPath.includes(path.join(".opencode", "bin"))) return "curl" as Method
-          if (process.execPath.includes(path.join(".local", "bin"))) return "curl" as Method
-          const exec = process.execPath.toLowerCase()
-
-          const checks: Array<{ name: Method; command: () => Effect.Effect }> = [
-            { name: "npm", command: () => text(["npm", "list", "-g", "--depth=0"]) },
-            { name: "yarn", command: () => text(["yarn", "global", "list"]) },
-            { name: "pnpm", command: () => text(["pnpm", "list", "-g", "--depth=0"]) },
-            { name: "bun", command: () => text(["bun", "pm", "ls", "-g"]) },
-            { name: "brew", command: () => text(["brew", "list", "--formula", "opencode"]) },
-            { name: "scoop", command: () => text(["scoop", "list", "opencode"]) },
-            { name: "choco", command: () => text(["choco", "list", "--limit-output", "opencode"]) },
-          ]
-
-          checks.sort((a, b) => {
-            const aMatches = exec.includes(a.name)
-            const bMatches = exec.includes(b.name)
-            if (aMatches && !bMatches) return -1
-            if (!aMatches && bMatches) return 1
-            return 0
-          })
-
-          for (const check of checks) {
-            const output = yield* check.command()
-            const installedName =
-              check.name === "brew" || check.name === "choco" || check.name === "scoop" ? "opencode" : "opencode-ai"
-            if (output.includes(installedName)) {
-              return check.name
-            }
-          }
-
-          return "unknown" as Method
+          }),
+        )
+        return {
+          code: result.exitCode,
+          stdout: result.stdout.toString("utf8"),
+          stderr: result.stderr.toString("utf8"),
+        }
+      },
+      Effect.catch(() => Effect.succeed({ code: 1, stdout: "", stderr: "" })),
+    )
+
+    const getBrewFormula = Effect.fnUntraced(function* () {
+      const tapFormula = yield* text(["brew", "list", "--formula", "anomalyco/tap/opencode"])
+      if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode"
+      const coreFormula = yield* text(["brew", "list", "--formula", "opencode"])
+      if (coreFormula.includes("opencode")) return "opencode"
+      return "opencode"
+    })
+
+    const upgradeCurl = Effect.fnUntraced(function* (target: string) {
+      const response = yield* httpOk.execute(HttpClientRequest.get("https://opencode.ai/install"))
+      const body = yield* response.text
+      const bodyBytes = new TextEncoder().encode(body)
+      const result = yield* appProcess.run(
+        ChildProcess.make("bash", [], {
+          stdin: Stream.make(bodyBytes),
+          env: { VERSION: target },
+          extendEnv: true,
         }),
-        latest: Effect.fn("Installation.latest")(function* (installMethod?: Method) {
-          const detectedMethod = installMethod || (yield* result.method())
-
-          if (detectedMethod === "brew") {
-            const formula = yield* getBrewFormula()
-            if (formula.includes("/")) {
-              const infoJson = yield* text(["brew", "info", "--json=v2", formula])
-              const info = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(BrewInfoV2))(infoJson)
-              return info.formulae[0].versions.stable
-            }
-            const response = yield* httpOk.execute(
-              HttpClientRequest.get("https://formulae.brew.sh/api/formula/opencode.json").pipe(
-                HttpClientRequest.acceptJson,
-              ),
-            )
-            const data = yield* HttpClientResponse.schemaBodyJson(BrewFormula)(response)
-            return data.versions.stable
+      )
+      return {
+        code: result.exitCode,
+        stdout: result.stdout.toString("utf8"),
+        stderr: result.stderr.toString("utf8"),
+      }
+    }, Effect.orDie)
+
+    const result: Interface = {
+      info: Effect.fn("Installation.info")(function* () {
+        return {
+          version: InstallationVersion,
+          latest: yield* result.latest(),
+        }
+      }),
+      method: Effect.fn("Installation.method")(function* () {
+        if (process.execPath.includes(path.join(".opencode", "bin"))) return "curl" as Method
+        if (process.execPath.includes(path.join(".local", "bin"))) return "curl" as Method
+        const exec = process.execPath.toLowerCase()
+
+        const checks: Array<{ name: Method; command: () => Effect.Effect }> = [
+          { name: "npm", command: () => text(["npm", "list", "-g", "--depth=0"]) },
+          { name: "yarn", command: () => text(["yarn", "global", "list"]) },
+          { name: "pnpm", command: () => text(["pnpm", "list", "-g", "--depth=0"]) },
+          { name: "bun", command: () => text(["bun", "pm", "ls", "-g"]) },
+          { name: "brew", command: () => text(["brew", "list", "--formula", "opencode"]) },
+          { name: "scoop", command: () => text(["scoop", "list", "opencode"]) },
+          { name: "choco", command: () => text(["choco", "list", "--limit-output", "opencode"]) },
+        ]
+
+        checks.sort((a, b) => {
+          const aMatches = exec.includes(a.name)
+          const bMatches = exec.includes(b.name)
+          if (aMatches && !bMatches) return -1
+          if (!aMatches && bMatches) return 1
+          return 0
+        })
+
+        for (const check of checks) {
+          const output = yield* check.command()
+          const installedName =
+            check.name === "brew" || check.name === "choco" || check.name === "scoop" ? "opencode" : "opencode-ai"
+          if (output.includes(installedName)) {
+            return check.name
           }
-
-          if (detectedMethod === "npm" || detectedMethod === "bun" || detectedMethod === "pnpm") {
-            const response = yield* httpOk.execute(
-              HttpClientRequest.get(
-                `${yield* NpmConfig.registry(process.cwd())}/opencode-ai/${InstallationChannel}`,
-              ).pipe(HttpClientRequest.acceptJson),
-            )
-            const data = yield* HttpClientResponse.schemaBodyJson(NpmPackage)(response)
-            return data.version
+        }
+
+        return "unknown" as Method
+      }),
+      latest: Effect.fn("Installation.latest")(function* (installMethod?: Method) {
+        const detectedMethod = installMethod || (yield* result.method())
+
+        if (detectedMethod === "brew") {
+          const formula = yield* getBrewFormula()
+          if (formula.includes("/")) {
+            const infoJson = yield* text(["brew", "info", "--json=v2", formula])
+            const info = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(BrewInfoV2))(infoJson)
+            return info.formulae[0].versions.stable
           }
+          const response = yield* httpOk.execute(
+            HttpClientRequest.get("https://formulae.brew.sh/api/formula/opencode.json").pipe(
+              HttpClientRequest.acceptJson,
+            ),
+          )
+          const data = yield* HttpClientResponse.schemaBodyJson(BrewFormula)(response)
+          return data.versions.stable
+        }
 
-          if (detectedMethod === "choco") {
-            const response = yield* httpOk.execute(
-              HttpClientRequest.get(
-                "https://community.chocolatey.org/api/v2/Packages?$filter=Id%20eq%20%27opencode%27%20and%20IsLatestVersion&$select=Version",
-              ).pipe(HttpClientRequest.setHeaders({ Accept: "application/json;odata=verbose" })),
-            )
-            const data = yield* HttpClientResponse.schemaBodyJson(ChocoPackage)(response)
-            return data.d.results[0].Version
-          }
+        if (detectedMethod === "npm" || detectedMethod === "bun" || detectedMethod === "pnpm") {
+          const response = yield* httpOk.execute(
+            HttpClientRequest.get(
+              `${yield* NpmConfig.registry(process.cwd())}/opencode-ai/${InstallationChannel}`,
+            ).pipe(HttpClientRequest.acceptJson),
+          )
+          const data = yield* HttpClientResponse.schemaBodyJson(NpmPackage)(response)
+          return data.version
+        }
 
-          if (detectedMethod === "scoop") {
-            const response = yield* httpOk.execute(
-              HttpClientRequest.get(
-                "https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/opencode.json",
-              ).pipe(HttpClientRequest.setHeaders({ Accept: "application/json" })),
-            )
-            const data = yield* HttpClientResponse.schemaBodyJson(ScoopManifest)(response)
-            return data.version
-          }
+        if (detectedMethod === "choco") {
+          const response = yield* httpOk.execute(
+            HttpClientRequest.get(
+              "https://community.chocolatey.org/api/v2/Packages?$filter=Id%20eq%20%27opencode%27%20and%20IsLatestVersion&$select=Version",
+            ).pipe(HttpClientRequest.setHeaders({ Accept: "application/json;odata=verbose" })),
+          )
+          const data = yield* HttpClientResponse.schemaBodyJson(ChocoPackage)(response)
+          return data.d.results[0].Version
+        }
 
+        if (detectedMethod === "scoop") {
           const response = yield* httpOk.execute(
-            HttpClientRequest.get("https://api.github.com/repos/anomalyco/opencode/releases/latest").pipe(
-              HttpClientRequest.acceptJson,
-            ),
+            HttpClientRequest.get(
+              "https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/opencode.json",
+            ).pipe(HttpClientRequest.setHeaders({ Accept: "application/json" })),
           )
-          const data = yield* HttpClientResponse.schemaBodyJson(GitHubRelease)(response)
-          return data.tag_name.replace(/^v/, "")
-        }, Effect.orDie),
-        upgrade: Effect.fn("Installation.upgrade")(function* (m: Method, target: string) {
-          let upgradeResult: { code: ChildProcessSpawner.ExitCode; stdout: string; stderr: string } | undefined
-          switch (m) {
-            case "curl":
-              upgradeResult = yield* upgradeCurl(target)
-              break
-            case "npm":
-              upgradeResult = yield* run(["npm", "install", "-g", `opencode-ai@${target}`])
-              break
-            case "pnpm":
-              upgradeResult = yield* run(["pnpm", "install", "-g", `opencode-ai@${target}`])
-              break
-            case "bun":
-              upgradeResult = yield* run(["bun", "install", "-g", `opencode-ai@${target}`])
-              break
-            case "brew": {
-              const formula = yield* getBrewFormula()
-              const env = { HOMEBREW_NO_AUTO_UPDATE: "1" }
-              if (formula.includes("/")) {
-                const tap = yield* run(["brew", "tap", "anomalyco/tap"], { env })
-                if (tap.code !== 0) {
-                  upgradeResult = tap
+          const data = yield* HttpClientResponse.schemaBodyJson(ScoopManifest)(response)
+          return data.version
+        }
+
+        const response = yield* httpOk.execute(
+          HttpClientRequest.get("https://api.github.com/repos/anomalyco/opencode/releases/latest").pipe(
+            HttpClientRequest.acceptJson,
+          ),
+        )
+        const data = yield* HttpClientResponse.schemaBodyJson(GitHubRelease)(response)
+        return data.tag_name.replace(/^v/, "")
+      }, Effect.orDie),
+      upgrade: Effect.fn("Installation.upgrade")(function* (m: Method, target: string) {
+        let upgradeResult: { code: number; stdout: string; stderr: string } | undefined
+        switch (m) {
+          case "curl":
+            upgradeResult = yield* upgradeCurl(target)
+            break
+          case "npm":
+            upgradeResult = yield* run(["npm", "install", "-g", `opencode-ai@${target}`])
+            break
+          case "pnpm":
+            upgradeResult = yield* run(["pnpm", "install", "-g", `opencode-ai@${target}`])
+            break
+          case "bun":
+            upgradeResult = yield* run(["bun", "install", "-g", `opencode-ai@${target}`])
+            break
+          case "brew": {
+            const formula = yield* getBrewFormula()
+            const env = { HOMEBREW_NO_AUTO_UPDATE: "1" }
+            if (formula.includes("/")) {
+              const tap = yield* run(["brew", "tap", "anomalyco/tap"], { env })
+              if (tap.code !== 0) {
+                upgradeResult = tap
+                break
+              }
+              const repo = yield* text(["brew", "--repo", "anomalyco/tap"])
+              const dir = repo.trim()
+              if (dir) {
+                const pull = yield* run(["git", "pull", "--ff-only"], { cwd: dir, env })
+                if (pull.code !== 0) {
+                  upgradeResult = pull
                   break
                 }
-                const repo = yield* text(["brew", "--repo", "anomalyco/tap"])
-                const dir = repo.trim()
-                if (dir) {
-                  const pull = yield* run(["git", "pull", "--ff-only"], { cwd: dir, env })
-                  if (pull.code !== 0) {
-                    upgradeResult = pull
-                    break
-                  }
-                }
               }
-              upgradeResult = yield* run(["brew", "upgrade", formula], { env })
-              break
             }
-            case "choco":
-              upgradeResult = yield* run(["choco", "upgrade", "opencode", `--version=${target}`, "-y"])
-              break
-            case "scoop":
-              upgradeResult = yield* run(["scoop", "install", `opencode@${target}`])
-              break
-            default:
-              return yield* new UpgradeFailedError({ stderr: `Unknown method: ${m}` })
-          }
-          if (!upgradeResult || upgradeResult.code !== 0) {
-            const stderr = m === "choco" ? "not running from an elevated command shell" : upgradeResult?.stderr || ""
-            return yield* new UpgradeFailedError({ stderr })
+            upgradeResult = yield* run(["brew", "upgrade", formula], { env })
+            break
           }
-          log.info("upgraded", {
-            method: m,
-            target,
-            stdout: upgradeResult.stdout,
-            stderr: upgradeResult.stderr,
-          })
-          yield* text([process.execPath, "--version"])
-        }),
-      }
-
-      return Service.of(result)
-    }),
-  )
-
-export const defaultLayer = layer.pipe(
-  Layer.provide(FetchHttpClient.layer),
-  Layer.provide(CrossSpawnSpawner.defaultLayer),
+          case "choco":
+            upgradeResult = yield* run(["choco", "upgrade", "opencode", `--version=${target}`, "-y"])
+            break
+          case "scoop":
+            upgradeResult = yield* run(["scoop", "install", `opencode@${target}`])
+            break
+          default:
+            return yield* new UpgradeFailedError({ stderr: `Unknown method: ${m}` })
+        }
+        if (!upgradeResult || upgradeResult.code !== 0) {
+          const stderr = m === "choco" ? "not running from an elevated command shell" : upgradeResult?.stderr || ""
+          return yield* new UpgradeFailedError({ stderr })
+        }
+        log.info("upgraded", {
+          method: m,
+          target,
+          stdout: upgradeResult.stdout,
+          stderr: upgradeResult.stderr,
+        })
+        yield* text([process.execPath, "--version"])
+      }),
+    }
+
+    return Service.of(result)
+  }),
 )
 
+export const defaultLayer = layer.pipe(Layer.provide(FetchHttpClient.layer), Layer.provide(AppProcess.defaultLayer))
+
 const { runPromise } = makeRuntime(Service, defaultLayer)
 
 export const latest = (...args: Parameters) => runPromise((s) => s.latest(...args))
diff --git a/packages/opencode/test/installation/installation.test.ts b/packages/opencode/test/installation/installation.test.ts
index 9ca38e968..8193ab8d1 100644
--- a/packages/opencode/test/installation/installation.test.ts
+++ b/packages/opencode/test/installation/installation.test.ts
@@ -4,6 +4,7 @@ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstab
 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
 import { Installation } from "../../src/installation"
 import { InstallationChannel } from "@opencode-ai/core/installation/version"
+import { AppProcess } from "@opencode-ai/core/process"
 import { testEffect } from "../lib/effect"
 
 const encoder = new TextEncoder()
@@ -47,7 +48,8 @@ function testLayer(
   httpHandler: (request: HttpClientRequest.HttpClientRequest) => Response,
   spawnHandler?: (cmd: string, args: readonly string[]) => string,
 ) {
-  return Installation.layer.pipe(Layer.provide(mockHttpClient(httpHandler)), Layer.provide(mockSpawner(spawnHandler)))
+  const appProcess = AppProcess.layer.pipe(Layer.provide(mockSpawner(spawnHandler)))
+  return Installation.layer.pipe(Layer.provide(mockHttpClient(httpHandler)), Layer.provide(appProcess))
 }
 
 describe("installation", () => {

From e5d13d9519ac7c8d9d1a32c31b33b0c8a31b7af2 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 12:04:51 -0400
Subject: [PATCH 280/378] effect(git): migrate to AppProcess.run (#27190)

---
 packages/opencode/src/git/index.ts | 58 +++++++++++-------------------
 1 file changed, 20 insertions(+), 38 deletions(-)

diff --git a/packages/opencode/src/git/index.ts b/packages/opencode/src/git/index.ts
index 349bbad46..4c88edaea 100644
--- a/packages/opencode/src/git/index.ts
+++ b/packages/opencode/src/git/index.ts
@@ -1,6 +1,6 @@
-import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
+import { AppProcess } from "@opencode-ai/core/process"
 import { Effect, Layer, Context, Stream } from "effect"
-import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
+import { ChildProcess } from "effect/unstable/process"
 
 const cfg = [
   "--no-optional-locks",
@@ -102,49 +102,31 @@ export class Service extends Context.Service()("@opencode/Gi
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {
-    const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
+    const appProcess = yield* AppProcess.Service
     const encoder = new TextEncoder()
     const stdin = (text: string) => Stream.make(encoder.encode(text))
 
     const run = Effect.fn("Git.run")(
       function* (args: string[], opts: Options) {
-        const proc = ChildProcess.make("git", [...cfg, ...args], {
-          cwd: opts.cwd,
-          env: opts.env,
-          extendEnv: true,
-          stdin: opts.stdin ?? "ignore",
-          stdout: "pipe",
-          stderr: "pipe",
-        })
-        const handle = yield* spawner.spawn(proc)
-        const collect = (stream: typeof handle.stdout) =>
-          Stream.runFold(
-            stream,
-            () => ({ chunks: [] as Uint8Array[], bytes: 0, truncated: false }),
-            (acc, chunk) => {
-              if (opts.maxOutputBytes === undefined) {
-                acc.chunks.push(chunk)
-                acc.bytes += chunk.length
-                return acc
-              }
-
-              const remaining = opts.maxOutputBytes - acc.bytes
-              if (remaining > 0) acc.chunks.push(remaining >= chunk.length ? chunk : chunk.slice(0, remaining))
-              acc.bytes += chunk.length
-              acc.truncated = acc.truncated || acc.bytes > opts.maxOutputBytes
-              return acc
-            },
-          ).pipe(Effect.map((x) => ({ buffer: Buffer.concat(x.chunks), truncated: x.truncated })))
-        const [stdout, stderr] = yield* Effect.all([collect(handle.stdout), collect(handle.stderr)], { concurrency: 2 })
+        const result = yield* appProcess.run(
+          ChildProcess.make("git", [...cfg, ...args], {
+            cwd: opts.cwd,
+            env: opts.env,
+            extendEnv: true,
+            stdin: opts.stdin ?? "ignore",
+            stdout: "pipe",
+            stderr: "pipe",
+          }),
+          { maxOutputBytes: opts.maxOutputBytes },
+        )
         return {
-          exitCode: yield* handle.exitCode,
-          text: () => stdout.buffer.toString("utf8"),
-          stdout: stdout.buffer,
-          stderr: stderr.buffer,
-          truncated: stdout.truncated || stderr.truncated,
+          exitCode: result.exitCode,
+          text: () => result.stdout.toString("utf8"),
+          stdout: result.stdout,
+          stderr: result.stderr,
+          truncated: result.truncated,
         } satisfies Result
       },
-      Effect.scoped,
       Effect.catch((err) => Effect.succeed(fail(err))),
     )
 
@@ -360,6 +342,6 @@ export const layer = Layer.effect(
   }),
 )
 
-export const defaultLayer = layer.pipe(Layer.provide(CrossSpawnSpawner.defaultLayer))
+export const defaultLayer = layer.pipe(Layer.provide(AppProcess.defaultLayer))
 
 export * as Git from "."

From 655b25bccf3f31dfd14a8626bf8119b6905275ea Mon Sep 17 00:00:00 2001
From: Frank 
Date: Wed, 13 May 2026 12:05:28 -0400
Subject: [PATCH 281/378] sync

---
 packages/console/function/src/stat.ts | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/packages/console/function/src/stat.ts b/packages/console/function/src/stat.ts
index 9a1a1cc14..54d38bc31 100644
--- a/packages/console/function/src/stat.ts
+++ b/packages/console/function/src/stat.ts
@@ -1,17 +1,16 @@
 import { and, Database, inArray } from "@opencode-ai/console-core/drizzle/index.js"
 import { ModelTpsRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
 
-type Entry = { provider: string; model: string; tps: number }
-type Result = Record
+type Result = Record>
 
 export default {
   async fetch(request: Request) {
     if (request.method !== "POST") return new Response("Method Not Allowed", { status: 405 })
 
-    const entries = (await request.json()) as Entry[]
-    if (!Array.isArray(entries) || entries.length === 0) return Response.json({} satisfies Result)
+    const body = (await request.json()) as { ids: string[] }
+    const ids = body.ids
 
-    const ids = entries.map((e) => `${e.provider}/${e.model}/${e.tps}`)
+    if (ids.length === 0) return Response.json({} satisfies Result)
 
     const toInterval = (date: Date) =>
       parseInt(
@@ -21,19 +20,23 @@ export default {
           .substring(0, 12),
       )
     const now = Date.now()
-    const intervals = Array.from({ length: 5 }, (_, i) => toInterval(new Date(now - i * 60 * 1000)))
+    const intervals = Array.from({ length: 30 }, (_, i) => toInterval(new Date(now - i * 60 * 1000)))
 
     const rows = await Database.use((tx) =>
       tx
         .select()
         .from(ModelTpsRateLimitTable)
-        .where(and(inArray(ModelTpsRateLimitTable.id, ids), inArray(ModelTpsRateLimitTable.interval, intervals))),
+        .where(and(inArray(ModelTpsRateLimitTable.id, body.ids), inArray(ModelTpsRateLimitTable.interval, intervals))),
     )
 
-    const result: Result = Object.fromEntries(ids.map((id) => [id, { qualify: 0, unqualify: 0 }]))
+    const result: Result = Object.fromEntries(
+      body.ids.map((id) => [
+        id,
+        Object.fromEntries(intervals.map((interval) => [interval, { qualify: 0, unqualify: 0 }])),
+      ]),
+    )
     for (const row of rows) {
-      result[row.id].qualify += row.qualify
-      result[row.id].unqualify += row.unqualify
+      result[row.id][row.interval] = { qualify: row.qualify, unqualify: row.unqualify }
     }
     return Response.json(result)
   },

From 25de3e407b15636f58cc43d29e0e3b7fba6f641a Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 12:30:13 -0400
Subject: [PATCH 282/378] test(acp): use shared instance fixture for event
 tests (#27351)

---
 .../test/acp/event-subscription.test.ts       | 27 +++++++++----------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/packages/opencode/test/acp/event-subscription.test.ts b/packages/opencode/test/acp/event-subscription.test.ts
index 791b5c578..2c9371d74 100644
--- a/packages/opencode/test/acp/event-subscription.test.ts
+++ b/packages/opencode/test/acp/event-subscription.test.ts
@@ -8,8 +8,7 @@ import type {
   ToolStatePending,
   ToolStateRunning,
 } from "@opencode-ai/sdk/v2"
-import { WithInstance } from "../../src/project/with-instance"
-import { tmpdir } from "../fixture/fixture"
+import { provideTestInstance, tmpdir } from "../fixture/fixture"
 
 type SessionUpdateParams = Parameters[0]
 type RequestPermissionParams = Parameters[0]
@@ -317,7 +316,7 @@ function createFakeAgent() {
 describe("acp.agent event subscription", () => {
   test("routes message.part.delta by the event sessionID (no cross-session pollution)", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, updates, stop } = createFakeAgent()
@@ -352,7 +351,7 @@ describe("acp.agent event subscription", () => {
 
   test("does not emit user_message_chunk for live prompt parts", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()
@@ -392,7 +391,7 @@ describe("acp.agent event subscription", () => {
 
   test("keeps concurrent sessions isolated when message.part.delta events are interleaved", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, chunks, stop } = createFakeAgent()
@@ -444,7 +443,7 @@ describe("acp.agent event subscription", () => {
 
   test("does not create additional event subscriptions on repeated loadSession()", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, calls, stop } = createFakeAgent()
@@ -466,7 +465,7 @@ describe("acp.agent event subscription", () => {
 
   test("permission.asked events are handled and replied", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const permissionReplies: string[] = []
@@ -505,7 +504,7 @@ describe("acp.agent event subscription", () => {
 
   test("permission prompt on session A does not block message updates for session B", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const permissionReplies: string[] = []
@@ -592,7 +591,7 @@ describe("acp.agent event subscription", () => {
 
   test("streams running bash output snapshots and de-dupes identical snapshots", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()
@@ -626,7 +625,7 @@ describe("acp.agent event subscription", () => {
 
   test("emits synthetic pending before first running update for any tool", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()
@@ -671,7 +670,7 @@ describe("acp.agent event subscription", () => {
 
   test("emits image attachments as ACP tool content blocks on live completed tool updates", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()
@@ -728,7 +727,7 @@ describe("acp.agent event subscription", () => {
 
   test("replays completed tool image attachments as ACP tool content blocks", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, sessionUpdates, stop, sdk } = createFakeAgent()
@@ -795,7 +794,7 @@ describe("acp.agent event subscription", () => {
 
   test("does not emit duplicate synthetic pending after replayed running tool", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop, sdk } = createFakeAgent()
@@ -854,7 +853,7 @@ describe("acp.agent event subscription", () => {
 
   test("clears bash snapshot marker on pending state", async () => {
     await using tmp = await tmpdir()
-    await WithInstance.provide({
+    await provideTestInstance({
       directory: tmp.path,
       fn: async () => {
         const { agent, controller, sessionUpdates, stop } = createFakeAgent()

From f0635e365fc7d49261d8f7d1e006c51a9b340732 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 12:33:19 -0400
Subject: [PATCH 283/378] test(session): use Effect polling in processor tests
 (#27354)

---
 .../test/session/processor-effect.test.ts     | 36 ++++++++++---------
 1 file changed, 19 insertions(+), 17 deletions(-)

diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts
index 537665b9a..61c566eae 100644
--- a/packages/opencode/test/session/processor-effect.test.ts
+++ b/packages/opencode/test/session/processor-effect.test.ts
@@ -105,6 +105,17 @@ function defer() {
   return { promise, resolve }
 }
 
+const waitFor = (check: Effect.Effect, message: string) =>
+  Effect.gen(function* () {
+    const stop = Date.now() + 500
+    while (Date.now() < stop) {
+      const value = yield* check
+      if (value !== undefined) return value
+      yield* Effect.sleep("10 millis")
+    }
+    return yield* Effect.fail(new Error(message))
+  })
+
 const user = Effect.fn("TestSession.user")(function* (sessionID: SessionID, text: string) {
   const session = yield* Session.Service
   const msg = yield* session.updateMessage({
@@ -301,15 +312,10 @@ it.live("session.processor effect tests preserve text start time", () =>
           })
           .pipe(Effect.forkChild)
 
-        yield* Effect.promise(async () => {
-          const stop = Date.now() + 500
-          while (Date.now() < stop) {
-            const text = MessageV2.parts(msg.id).find((part): part is MessageV2.TextPart => part.type === "text")
-            if (text?.time?.start) return
-            await Bun.sleep(10)
-          }
-          throw new Error("timed out waiting for text part")
-        })
+        yield* waitFor(
+          Effect.sync(() => MessageV2.parts(msg.id).find((part): part is MessageV2.TextPart => part.type === "text")),
+          "timed out waiting for text part",
+        )
         yield* Effect.sleep("20 millis")
         gate.resolve()
 
@@ -691,14 +697,10 @@ it.live("session.processor effect tests mark pending tools as aborted on cleanup
           .pipe(Effect.forkChild)
 
         yield* llm.wait(1)
-        yield* Effect.promise(async () => {
-          const end = Date.now() + 500
-          while (Date.now() < end) {
-            const parts = await MessageV2.parts(msg.id)
-            if (parts.some((part) => part.type === "tool")) return
-            await Bun.sleep(10)
-          }
-        })
+        yield* waitFor(
+          Effect.sync(() => MessageV2.parts(msg.id).find((part): part is MessageV2.ToolPart => part.type === "tool")),
+          "timed out waiting for tool part",
+        )
         yield* Fiber.interrupt(run)
 
         const exit = yield* Fiber.await(run)

From 533495ae20ae154f5f8bcb0764636979a4427c77 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 12:38:37 -0400
Subject: [PATCH 284/378] test(mcp): migrate OAuth auto-connect tests (#27356)

---
 .../test/mcp/oauth-auto-connect.test.ts       | 290 ++++++++----------
 1 file changed, 122 insertions(+), 168 deletions(-)

diff --git a/packages/opencode/test/mcp/oauth-auto-connect.test.ts b/packages/opencode/test/mcp/oauth-auto-connect.test.ts
index 3cf677421..6fb15c459 100644
--- a/packages/opencode/test/mcp/oauth-auto-connect.test.ts
+++ b/packages/opencode/test/mcp/oauth-auto-connect.test.ts
@@ -1,5 +1,6 @@
-import { test, expect, mock, beforeEach } from "bun:test"
-import { Effect } from "effect"
+import { expect, mock, beforeEach } from "bun:test"
+import { Effect, Layer } from "effect"
+import { testEffect } from "../lib/effect"
 
 // Mock UnauthorizedError to match the SDK's class
 class MockUnauthorizedError extends Error {
@@ -111,172 +112,125 @@ beforeEach(() => {
 
 // Import modules after mocking
 const { MCP } = await import("../../src/mcp/index")
-const { Instance } = await import("../../src/project/instance")
-const { WithInstance } = await import("../../src/project/with-instance")
-const { tmpdir } = await import("../fixture/fixture")
-
-test("first connect to OAuth server shows needs_auth instead of failed", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      await Bun.write(
-        `${dir}/opencode.json`,
-        JSON.stringify({
-          $schema: "https://opencode.ai/config.json",
-          mcp: {
-            "test-oauth": {
-              type: "remote",
-              url: "https://example.com/mcp",
-            },
-          },
-        }),
-      )
-    },
-  })
-
-  await WithInstance.provide({
-    directory: tmp.path,
-    fn: async () => {
-      const result = await Effect.runPromise(
-        MCP.Service.use((mcp) =>
-          mcp.add("test-oauth", {
-            type: "remote",
-            url: "https://example.com/mcp",
-          }),
-        ).pipe(Effect.provide(MCP.defaultLayer)),
-      )
-
-      const serverStatus = result.status as Record
-
-      // The server should be detected as needing auth, NOT as failed.
-      // Before the fix, provider.state() would throw a plain Error
-      // ("No OAuth state saved for MCP server: test-oauth") which was
-      // not caught as UnauthorizedError, causing status to be "failed".
-      expect(serverStatus["test-oauth"]).toBeDefined()
-      expect(serverStatus["test-oauth"].status).toBe("needs_auth")
-    },
-  })
-})
-
-test("state() generates a new state when none is saved", async () => {
-  const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider")
-  const { McpAuth } = await import("../../src/mcp/auth")
-
-  await using tmp = await tmpdir()
-
-  await WithInstance.provide({
-    directory: tmp.path,
-    fn: async () => {
-      const auth = await Effect.runPromise(
-        Effect.gen(function* () {
-          return yield* McpAuth.Service
-        }).pipe(Effect.provide(McpAuth.defaultLayer)),
-      )
-      const provider = new McpOAuthProvider(
-        "test-state-gen",
-        "https://example.com/mcp",
-        {},
-        { onRedirect: async () => {} },
-        auth,
-      )
-
-      const entryBefore = await Effect.runPromise(
-        McpAuth.Service.use((auth) => auth.get("test-state-gen")).pipe(Effect.provide(McpAuth.defaultLayer)),
-      )
-      expect(entryBefore?.oauthState).toBeUndefined()
-
-      // state() should generate and return a new state, not throw
-      const state = await provider.state()
-      expect(typeof state).toBe("string")
-      expect(state.length).toBe(64) // 32 bytes as hex
-
-      // The generated state should be persisted
-      const entryAfter = await Effect.runPromise(
-        McpAuth.Service.use((auth) => auth.get("test-state-gen")).pipe(Effect.provide(McpAuth.defaultLayer)),
-      )
-      expect(entryAfter?.oauthState).toBe(state)
-    },
-  })
-})
-
-test("state() returns existing state when one is saved", async () => {
-  const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider")
-  const { McpAuth } = await import("../../src/mcp/auth")
-
-  await using tmp = await tmpdir()
-
-  await WithInstance.provide({
-    directory: tmp.path,
-    fn: async () => {
-      const auth = await Effect.runPromise(
-        Effect.gen(function* () {
-          return yield* McpAuth.Service
-        }).pipe(Effect.provide(McpAuth.defaultLayer)),
-      )
-      const provider = new McpOAuthProvider(
-        "test-state-existing",
-        "https://example.com/mcp",
-        {},
-        { onRedirect: async () => {} },
-        auth,
-      )
-
-      // Pre-save a state
-      const existingState = "pre-saved-state-value"
-      await Effect.runPromise(
-        McpAuth.Service.use((auth) => auth.updateOAuthState("test-state-existing", existingState)).pipe(
-          Effect.provide(McpAuth.defaultLayer),
-        ),
-      )
-
-      // state() should return the existing state
-      const state = await provider.state()
-      expect(state).toBe(existingState)
+const { Bus } = await import("../../src/bus")
+const { Config } = await import("../../src/config/config")
+const { McpAuth } = await import("../../src/mcp/auth")
+const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider")
+const { AppFileSystem } = await import("@opencode-ai/core/filesystem")
+const { CrossSpawnSpawner } = await import("@opencode-ai/core/cross-spawn-spawner")
+
+const mcpTest = testEffect(
+  Layer.mergeAll(
+    MCP.layer.pipe(
+      Layer.provide(McpAuth.defaultLayer),
+      Layer.provideMerge(Bus.layer),
+      Layer.provide(Config.defaultLayer),
+      Layer.provide(CrossSpawnSpawner.defaultLayer),
+      Layer.provide(AppFileSystem.defaultLayer),
+    ),
+    McpAuth.defaultLayer,
+  ),
+)
+
+const config = (name: string) => ({
+  mcp: {
+    [name]: {
+      type: "remote" as const,
+      url: "https://example.com/mcp",
     },
-  })
+  },
 })
 
-test("authenticate() stores a connected client when auth completes without redirect", async () => {
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      await Bun.write(
-        `${dir}/opencode.json`,
-        JSON.stringify({
-          $schema: "https://opencode.ai/config.json",
-          mcp: {
-            "test-oauth-connect": {
-              type: "remote",
-              url: "https://example.com/mcp",
-            },
-          },
-        }),
-      )
-    },
-  })
-
-  await WithInstance.provide({
-    directory: tmp.path,
-    fn: async () => {
-      await Effect.runPromise(
-        MCP.Service.use((mcp) =>
-          Effect.gen(function* () {
-            const added = yield* mcp.add("test-oauth-connect", {
-              type: "remote",
-              url: "https://example.com/mcp",
-            })
-            const before = added.status as Record
-            expect(before["test-oauth-connect"]?.status).toBe("needs_auth")
-
-            simulateAuthFlow = false
-            connectSucceedsImmediately = true
-
-            const result = yield* mcp.authenticate("test-oauth-connect")
-            expect(result.status).toBe("connected")
-
-            const after = yield* mcp.status()
-            expect(after["test-oauth-connect"]?.status).toBe("connected")
-          }),
-        ).pipe(Effect.provide(MCP.defaultLayer)),
-      )
-    },
-  })
-})
+mcpTest.instance(
+  "first connect to OAuth server shows needs_auth instead of failed",
+  () =>
+    MCP.Service.use((mcp) =>
+      Effect.gen(function* () {
+        const result = yield* mcp.add("test-oauth", {
+          type: "remote",
+          url: "https://example.com/mcp",
+        })
+
+        const serverStatus = result.status as Record
+
+        // The server should be detected as needing auth, NOT as failed.
+        // Before the fix, provider.state() would throw a plain Error
+        // ("No OAuth state saved for MCP server: test-oauth") which was
+        // not caught as UnauthorizedError, causing status to be "failed".
+        expect(serverStatus["test-oauth"]).toBeDefined()
+        expect(serverStatus["test-oauth"].status).toBe("needs_auth")
+      }),
+    ),
+  { config: config("test-oauth") },
+)
+
+mcpTest.instance("state() generates a new state when none is saved", () =>
+  Effect.gen(function* () {
+    const auth = yield* McpAuth.Service
+    const provider = new McpOAuthProvider(
+      "test-state-gen",
+      "https://example.com/mcp",
+      {},
+      { onRedirect: async () => {} },
+      auth,
+    )
+
+    const entryBefore = yield* McpAuth.Service.use((auth) => auth.get("test-state-gen"))
+    expect(entryBefore?.oauthState).toBeUndefined()
+
+    // state() should generate and return a new state, not throw
+    const state = yield* Effect.promise(() => provider.state())
+    expect(typeof state).toBe("string")
+    expect(state.length).toBe(64) // 32 bytes as hex
+
+    // The generated state should be persisted
+    const entryAfter = yield* McpAuth.Service.use((auth) => auth.get("test-state-gen"))
+    expect(entryAfter?.oauthState).toBe(state)
+  }),
+)
+
+mcpTest.instance("state() returns existing state when one is saved", () =>
+  Effect.gen(function* () {
+    const auth = yield* McpAuth.Service
+    const provider = new McpOAuthProvider(
+      "test-state-existing",
+      "https://example.com/mcp",
+      {},
+      { onRedirect: async () => {} },
+      auth,
+    )
+
+    // Pre-save a state
+    const existingState = "pre-saved-state-value"
+    yield* McpAuth.Service.use((auth) => auth.updateOAuthState("test-state-existing", existingState))
+
+    // state() should return the existing state
+    const state = yield* Effect.promise(() => provider.state())
+    expect(state).toBe(existingState)
+  }),
+)
+
+mcpTest.instance(
+  "authenticate() stores a connected client when auth completes without redirect",
+  () =>
+    MCP.Service.use((mcp) =>
+      Effect.gen(function* () {
+        const added = yield* mcp.add("test-oauth-connect", {
+          type: "remote",
+          url: "https://example.com/mcp",
+        })
+        const before = added.status as Record
+        expect(before["test-oauth-connect"]?.status).toBe("needs_auth")
+
+        simulateAuthFlow = false
+        connectSucceedsImmediately = true
+
+        const result = yield* mcp.authenticate("test-oauth-connect")
+        expect(result.status).toBe("connected")
+
+        const after = yield* mcp.status()
+        expect(after["test-oauth-connect"]?.status).toBe("connected")
+      }),
+    ),
+  { config: config("test-oauth-connect") },
+)

From 8ad3a4b2170033a7bbabe98fa6dd82fe44d9cfaf Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 12:43:23 -0400
Subject: [PATCH 285/378] test(util): migrate log cleanup test to Effect
 (#27357)

---
 packages/opencode/test/util/log.test.ts | 71 +++++++++++++------------
 1 file changed, 38 insertions(+), 33 deletions(-)

diff --git a/packages/opencode/test/util/log.test.ts b/packages/opencode/test/util/log.test.ts
index 486ca0f3b..defd8c981 100644
--- a/packages/opencode/test/util/log.test.ts
+++ b/packages/opencode/test/util/log.test.ts
@@ -1,44 +1,49 @@
-import { afterEach, expect, test } from "bun:test"
+import { expect } from "bun:test"
+import { Effect } from "effect"
 import fs from "fs/promises"
 import path from "path"
+import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { Global } from "@opencode-ai/core/global"
 import * as Log from "@opencode-ai/core/util/log"
-import { tmpdir } from "../fixture/fixture"
-
-const log = Global.Path.log
-
-afterEach(() => {
-  Global.Path.log = log
-})
-
-async function files(dir: string) {
-  let last = ""
-  let same = 0
-
-  for (let i = 0; i < 50; i++) {
-    const list = (await fs.readdir(dir)).sort()
-    const next = JSON.stringify(list)
-    same = next === last ? same + 1 : 0
-    if (same >= 2 && list.length === 11) return list
-    last = next
-    await Bun.sleep(10)
-  }
-
-  return (await fs.readdir(dir)).sort()
+import { tmpdirScoped } from "../fixture/fixture"
+import { testEffect } from "../lib/effect"
+
+const it = testEffect(CrossSpawnSpawner.defaultLayer)
+
+function files(dir: string) {
+  return Effect.gen(function* () {
+    let last = ""
+    let same = 0
+
+    for (let i = 0; i < 50; i++) {
+      const list = yield* Effect.promise(() => fs.readdir(dir).then((files) => files.sort()))
+      const next = JSON.stringify(list)
+      same = next === last ? same + 1 : 0
+      if (same >= 2 && list.length === 11) return list
+      last = next
+      yield* Effect.sleep("10 millis")
+    }
+
+    return yield* Effect.promise(() => fs.readdir(dir).then((files) => files.sort()))
+  })
 }
 
-test("init cleanup keeps the newest timestamped logs", async () => {
-  await using tmp = await tmpdir()
-  Global.Path.log = tmp.path
+it.live("init cleanup keeps the newest timestamped logs", () =>
+  Effect.gen(function* () {
+    const log = Global.Path.log
+    yield* Effect.addFinalizer(() => Effect.sync(() => (Global.Path.log = log)))
+    const dir = yield* tmpdirScoped()
+    Global.Path.log = dir
 
-  const list = Array.from({ length: 12 }, (_, i) => `2000-01-${String(i + 1).padStart(2, "0")}T000000.log`)
+    const list = Array.from({ length: 12 }, (_, i) => `2000-01-${String(i + 1).padStart(2, "0")}T000000.log`)
 
-  await Promise.all(list.map((file) => fs.writeFile(path.join(tmp.path, file), file)))
+    yield* Effect.all(list.map((file) => Effect.promise(() => fs.writeFile(path.join(dir, file), file))))
 
-  await Log.init({ print: false, dev: false })
+    yield* Effect.promise(() => Log.init({ print: false, dev: false }))
 
-  const next = await files(tmp.path)
+    const next = yield* files(dir)
 
-  expect(next).not.toContain(list[0]!)
-  expect(next).toContain(list.at(-1)!)
-})
+    expect(next).not.toContain(list[0]!)
+    expect(next).toContain(list.at(-1)!)
+  }),
+)

From fa077b92b17df16824262efa72345a9b71d4d1ad Mon Sep 17 00:00:00 2001
From: Frank 
Date: Wed, 13 May 2026 12:45:08 -0400
Subject: [PATCH 286/378] zen: update sticky session logic

---
 .../app/src/routes/zen/util/handler.ts        |    4 +-
 .../routes/zen/util/stickyProviderTracker.ts  |   36 +-
 .../migration.sql                             |    7 +
 .../snapshot.json                             | 2821 +++++++++++++++++
 packages/console/core/src/schema/ip.sql.ts    |   10 +
 5 files changed, 2871 insertions(+), 7 deletions(-)
 create mode 100644 packages/console/core/migrations/20260513163955_tearful_whistler/migration.sql
 create mode 100644 packages/console/core/migrations/20260513163955_tearful_whistler/snapshot.json

diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts
index a550a6e4c..d9e564169 100644
--- a/packages/console/app/src/routes/zen/util/handler.ts
+++ b/packages/console/app/src/routes/zen/util/handler.ts
@@ -123,7 +123,7 @@ export async function handler(
       ? createIpRateLimiter(modelInfo.id, modelInfo.rateLimit, ip, input.request)
       : createKeyRateLimiter(modelInfo.id, modelInfo.rateLimit, zenApiKey, input.request)
     await rateLimiter?.check()
-    const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
+    const stickyTracker = createStickyTracker(modelInfo.id, modelInfo.stickyProvider, sessionId)
     const stickyProvider = await stickyTracker?.get()
     const authInfo = await authenticate(modelInfo, zenApiKey)
     const billingSource = validateBilling(authInfo, modelInfo)
@@ -238,7 +238,7 @@ export async function handler(
     dataDumper?.provideRequest(reqBody)
 
     // Store sticky provider
-    await stickyTracker?.set(providerInfo.id)
+    if (res.status === 200) await stickyTracker?.set(providerInfo.id)
 
     // Temporarily change 404 to 400 status code b/c solid start automatically override 404 response
     const resStatus = res.status === 404 ? 400 : res.status
diff --git a/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts b/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts
index 8029757c5..fae0cdf03 100644
--- a/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts
+++ b/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts
@@ -1,16 +1,42 @@
-import { Resource } from "@opencode-ai/console-resource"
+import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
+import { ModelStickyProviderTable } from "@opencode-ai/console-core/schema/ip.sql.js"
 
-export function createStickyTracker(stickyProvider: "strict" | "prefer" | undefined, session: string) {
+export function createStickyTracker(modelId: string, stickyProvider: "strict" | "prefer" | undefined, session: string) {
   if (!stickyProvider) return
   if (!session) return
-  const key = `sticky:${session}`
+  const id = `${modelId}/${session}`
+  let _providerId: string | undefined
 
   return {
     get: async () => {
-      return await Resource.GatewayKv.get(key)
+      const data = await Database.use((tx) =>
+        tx
+          .select({
+            providerId: ModelStickyProviderTable.providerId,
+          })
+          .from(ModelStickyProviderTable)
+          .where(eq(ModelStickyProviderTable.id, id))
+          .limit(1),
+      )
+      _providerId = data[0]?.providerId
+      return _providerId
     },
     set: async (providerId: string) => {
-      await Resource.GatewayKv.put(key, providerId, { expirationTtl: 86400 })
+      if (_providerId === providerId) return
+
+      await Database.use((tx) =>
+        tx
+          .insert(ModelStickyProviderTable)
+          .values({
+            id,
+            providerId,
+          })
+          .onDuplicateKeyUpdate({
+            set: {
+              providerId,
+            },
+          }),
+      )
     },
   }
 }
diff --git a/packages/console/core/migrations/20260513163955_tearful_whistler/migration.sql b/packages/console/core/migrations/20260513163955_tearful_whistler/migration.sql
new file mode 100644
index 000000000..a2bcc164c
--- /dev/null
+++ b/packages/console/core/migrations/20260513163955_tearful_whistler/migration.sql
@@ -0,0 +1,7 @@
+CREATE TABLE `model_sticky_provider` (
+	`id` varchar(255) PRIMARY KEY,
+	`time_created` timestamp(3) NOT NULL DEFAULT (now()),
+	`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+	`time_deleted` timestamp(3),
+	`provider_id` varchar(255) NOT NULL
+);
diff --git a/packages/console/core/migrations/20260513163955_tearful_whistler/snapshot.json b/packages/console/core/migrations/20260513163955_tearful_whistler/snapshot.json
new file mode 100644
index 000000000..7810ff281
--- /dev/null
+++ b/packages/console/core/migrations/20260513163955_tearful_whistler/snapshot.json
@@ -0,0 +1,2821 @@
+{
+  "version": "6",
+  "dialect": "mysql",
+  "id": "1f04bd59-35b0-493b-9d55-cfa08207ba8e",
+  "prevIds": [
+    "c742e0f2-5d89-4216-b843-059d00680f13"
+  ],
+  "ddl": [
+    {
+      "name": "account",
+      "entityType": "tables"
+    },
+    {
+      "name": "auth",
+      "entityType": "tables"
+    },
+    {
+      "name": "benchmark",
+      "entityType": "tables"
+    },
+    {
+      "name": "billing",
+      "entityType": "tables"
+    },
+    {
+      "name": "coupon",
+      "entityType": "tables"
+    },
+    {
+      "name": "lite",
+      "entityType": "tables"
+    },
+    {
+      "name": "payment",
+      "entityType": "tables"
+    },
+    {
+      "name": "subscription",
+      "entityType": "tables"
+    },
+    {
+      "name": "usage",
+      "entityType": "tables"
+    },
+    {
+      "name": "ip_rate_limit",
+      "entityType": "tables"
+    },
+    {
+      "name": "ip",
+      "entityType": "tables"
+    },
+    {
+      "name": "key_rate_limit",
+      "entityType": "tables"
+    },
+    {
+      "name": "model_sticky_provider",
+      "entityType": "tables"
+    },
+    {
+      "name": "model_tpm_rate_limit",
+      "entityType": "tables"
+    },
+    {
+      "name": "model_tps_rate_limit",
+      "entityType": "tables"
+    },
+    {
+      "name": "key",
+      "entityType": "tables"
+    },
+    {
+      "name": "model",
+      "entityType": "tables"
+    },
+    {
+      "name": "provider",
+      "entityType": "tables"
+    },
+    {
+      "name": "user",
+      "entityType": "tables"
+    },
+    {
+      "name": "workspace",
+      "entityType": "tables"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "account"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "account"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "account"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "account"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "enum('email','github','google')",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "provider",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "subject",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "account_id",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "varchar(64)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "model",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "varchar(64)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "agent",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "mediumtext",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "result",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "customer_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "payment_method_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(32)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "payment_method_type",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(4)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "payment_method_last4",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "bigint",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "balance",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_limit",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_usage",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_monthly_usage_updated",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "boolean",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reload",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reload_trigger",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reload_amount",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reload_error",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_reload_error",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_reload_locked_till",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "json",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "subscription",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(28)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "subscription_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "enum('20','100','200')",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "subscription_plan",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_subscription_booked",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_subscription_selected",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(28)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "lite_subscription_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "json",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "lite",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "email",
+      "entityType": "columns",
+      "table": "coupon"
+    },
+    {
+      "type": "enum('BUILDATHON','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100')",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "type",
+      "entityType": "columns",
+      "table": "coupon"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_redeemed",
+      "entityType": "columns",
+      "table": "coupon"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "user_id",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "rolling_usage",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "weekly_usage",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_usage",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_rolling_updated",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_weekly_updated",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_monthly_updated",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "customer_id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "invoice_id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "payment_id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "bigint",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "amount",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_refunded",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "json",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "enrichment",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "user_id",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "rolling_usage",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "fixed_usage",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_rolling_updated",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_fixed_updated",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "model",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "provider",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "input_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "output_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reasoning_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "cache_read_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "cache_write_5m_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "cache_write_1h_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "bigint",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "cost",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key_id",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "session_id",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "json",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "enrichment",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(45)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "ip",
+      "entityType": "columns",
+      "table": "ip_rate_limit"
+    },
+    {
+      "type": "varchar(10)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "ip_rate_limit"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "count",
+      "entityType": "columns",
+      "table": "ip_rate_limit"
+    },
+    {
+      "type": "varchar(45)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "ip",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "usage",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key",
+      "entityType": "columns",
+      "table": "key_rate_limit"
+    },
+    {
+      "type": "varchar(40)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "key_rate_limit"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "count",
+      "entityType": "columns",
+      "table": "key_rate_limit"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "model_sticky_provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "model_sticky_provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "model_sticky_provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "model_sticky_provider"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "provider_id",
+      "entityType": "columns",
+      "table": "model_sticky_provider"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "model_tpm_rate_limit"
+    },
+    {
+      "type": "bigint",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "model_tpm_rate_limit"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "count",
+      "entityType": "columns",
+      "table": "model_tpm_rate_limit"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "model_tps_rate_limit"
+    },
+    {
+      "type": "bigint",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "model_tps_rate_limit"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "qualify",
+      "entityType": "columns",
+      "table": "model_tps_rate_limit"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "unqualify",
+      "entityType": "columns",
+      "table": "model_tps_rate_limit"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "name",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "user_id",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_used",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "varchar(64)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "model",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "varchar(64)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "provider",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "text",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "credentials",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "account_id",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "email",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "name",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_seen",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "color",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "enum('admin','member')",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "role",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_limit",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_usage",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_monthly_usage_updated",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "slug",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "name",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "account",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "auth",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "benchmark",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "billing",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "email",
+        "type"
+      ],
+      "name": "PRIMARY",
+      "table": "coupon",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "lite",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "payment",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "subscription",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "usage",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "ip",
+        "interval"
+      ],
+      "name": "PRIMARY",
+      "table": "ip_rate_limit",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "ip"
+      ],
+      "name": "PRIMARY",
+      "table": "ip",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "key",
+        "interval"
+      ],
+      "name": "PRIMARY",
+      "table": "key_rate_limit",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "model_sticky_provider",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id",
+        "interval"
+      ],
+      "name": "PRIMARY",
+      "table": "model_tpm_rate_limit",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id",
+        "interval"
+      ],
+      "name": "PRIMARY",
+      "table": "model_tps_rate_limit",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "key",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "model",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "provider",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "user",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "workspace",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        {
+          "value": "provider",
+          "isExpression": false
+        },
+        {
+          "value": "subject",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "provider",
+      "entityType": "indexes",
+      "table": "auth"
+    },
+    {
+      "columns": [
+        {
+          "value": "account_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "account_id",
+      "entityType": "indexes",
+      "table": "auth"
+    },
+    {
+      "columns": [
+        {
+          "value": "time_created",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "time_created",
+      "entityType": "indexes",
+      "table": "benchmark"
+    },
+    {
+      "columns": [
+        {
+          "value": "customer_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_customer_id",
+      "entityType": "indexes",
+      "table": "billing"
+    },
+    {
+      "columns": [
+        {
+          "value": "subscription_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_subscription_id",
+      "entityType": "indexes",
+      "table": "billing"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "user_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "workspace_user_id",
+      "entityType": "indexes",
+      "table": "lite"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "user_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "workspace_user_id",
+      "entityType": "indexes",
+      "table": "subscription"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "time_created",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "usage_time_created",
+      "entityType": "indexes",
+      "table": "usage"
+    },
+    {
+      "columns": [
+        {
+          "value": "key",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_key",
+      "entityType": "indexes",
+      "table": "key"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "model",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "model_workspace_model",
+      "entityType": "indexes",
+      "table": "model"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "provider",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "workspace_provider",
+      "entityType": "indexes",
+      "table": "provider"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "account_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "user_account_id",
+      "entityType": "indexes",
+      "table": "user"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "email",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "user_email",
+      "entityType": "indexes",
+      "table": "user"
+    },
+    {
+      "columns": [
+        {
+          "value": "account_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_account_id",
+      "entityType": "indexes",
+      "table": "user"
+    },
+    {
+      "columns": [
+        {
+          "value": "email",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_email",
+      "entityType": "indexes",
+      "table": "user"
+    },
+    {
+      "columns": [
+        {
+          "value": "slug",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "slug",
+      "entityType": "indexes",
+      "table": "workspace"
+    }
+  ],
+  "renames": []
+}
\ No newline at end of file
diff --git a/packages/console/core/src/schema/ip.sql.ts b/packages/console/core/src/schema/ip.sql.ts
index 975dcfa18..a80fa474c 100644
--- a/packages/console/core/src/schema/ip.sql.ts
+++ b/packages/console/core/src/schema/ip.sql.ts
@@ -51,3 +51,13 @@ export const ModelTpsRateLimitTable = mysqlTable(
   },
   (table) => [primaryKey({ columns: [table.id, table.interval] })],
 )
+
+export const ModelStickyProviderTable = mysqlTable(
+  "model_sticky_provider",
+  {
+    id: varchar("id", { length: 255 }).notNull(),
+    ...timestamps,
+    providerId: varchar("provider_id", { length: 255 }).notNull(),
+  },
+  (table) => [primaryKey({ columns: [table.id] })],
+)

From 7cc968b05d120b4225e550104132f9cba3bd05f2 Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]" 
Date: Wed, 13 May 2026 16:47:21 +0000
Subject: [PATCH 287/378] chore: generate

---
 .../snapshot.json                             | 100 ++++--------------
 1 file changed, 22 insertions(+), 78 deletions(-)

diff --git a/packages/console/core/migrations/20260513163955_tearful_whistler/snapshot.json b/packages/console/core/migrations/20260513163955_tearful_whistler/snapshot.json
index 7810ff281..b79173522 100644
--- a/packages/console/core/migrations/20260513163955_tearful_whistler/snapshot.json
+++ b/packages/console/core/migrations/20260513163955_tearful_whistler/snapshot.json
@@ -2,9 +2,7 @@
   "version": "6",
   "dialect": "mysql",
   "id": "1f04bd59-35b0-493b-9d55-cfa08207ba8e",
-  "prevIds": [
-    "c742e0f2-5d89-4216-b843-059d00680f13"
-  ],
+  "prevIds": ["c742e0f2-5d89-4216-b843-059d00680f13"],
   "ddl": [
     {
       "name": "account",
@@ -2355,175 +2353,121 @@
       "table": "workspace"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "name": "PRIMARY",
       "table": "account",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "name": "PRIMARY",
       "table": "auth",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "name": "PRIMARY",
       "table": "benchmark",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "billing",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "email",
-        "type"
-      ],
+      "columns": ["email", "type"],
       "name": "PRIMARY",
       "table": "coupon",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "lite",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "payment",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "subscription",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "usage",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "ip",
-        "interval"
-      ],
+      "columns": ["ip", "interval"],
       "name": "PRIMARY",
       "table": "ip_rate_limit",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "ip"
-      ],
+      "columns": ["ip"],
       "name": "PRIMARY",
       "table": "ip",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "key",
-        "interval"
-      ],
+      "columns": ["key", "interval"],
       "name": "PRIMARY",
       "table": "key_rate_limit",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "name": "PRIMARY",
       "table": "model_sticky_provider",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id",
-        "interval"
-      ],
+      "columns": ["id", "interval"],
       "name": "PRIMARY",
       "table": "model_tpm_rate_limit",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id",
-        "interval"
-      ],
+      "columns": ["id", "interval"],
       "name": "PRIMARY",
       "table": "model_tps_rate_limit",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "key",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "model",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "provider",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "workspace_id",
-        "id"
-      ],
+      "columns": ["workspace_id", "id"],
       "name": "PRIMARY",
       "table": "user",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "name": "PRIMARY",
       "table": "workspace",
       "entityType": "pks"
@@ -2818,4 +2762,4 @@
     }
   ],
   "renames": []
-}
\ No newline at end of file
+}

From a4ebb07c25ce58ecc5599555267ea196ea8f21a1 Mon Sep 17 00:00:00 2001
From: Shoubhit Dash 
Date: Thu, 14 May 2026 00:09:53 +0530
Subject: [PATCH 288/378] refactor(flags): route llm client through runtime
 flags (#27368)

---
 packages/opencode/src/session/llm.ts | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index c31545a3d..116254a81 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -12,7 +12,6 @@ import type { Agent } from "@/agent/agent"
 import type { MessageV2 } from "./message-v2"
 import { Plugin } from "@/plugin"
 import { SystemPrompt } from "./system"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import { Permission } from "@/permission"
 import { PermissionID } from "@/permission/schema"
 import { Bus } from "@/bus"
@@ -21,6 +20,7 @@ import { SessionID } from "@/session/schema"
 import { Auth } from "@/auth"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
 import { EffectBridge } from "@/effect/bridge"
+import { RuntimeFlags } from "@/effect/runtime-flags"
 import * as Option from "effect/Option"
 import * as OtelTracer from "@effect/opentelemetry/Tracer"
 
@@ -62,7 +62,7 @@ export class Service extends Context.Service()("@opencode/LL
 const live: Layer.Layer<
   Service,
   never,
-  Auth.Service | Config.Service | Provider.Service | Plugin.Service | Permission.Service
+  Auth.Service | Config.Service | Provider.Service | Plugin.Service | Permission.Service | RuntimeFlags.Service
 > = Layer.effect(
   Service,
   Effect.gen(function* () {
@@ -71,6 +71,7 @@ const live: Layer.Layer<
     const provider = yield* Provider.Service
     const plugin = yield* Plugin.Service
     const perm = yield* Permission.Service
+    const flags = yield* RuntimeFlags.Service
 
     const run = Effect.fn("LLM.run")(function* (input: StreamRequest) {
       const l = log
@@ -375,7 +376,7 @@ const live: Layer.Layer<
                 "x-opencode-project": opencodeProjectID,
                 "x-opencode-session": input.sessionID,
                 "x-opencode-request": input.user.id,
-                "x-opencode-client": Flag.OPENCODE_CLIENT,
+                "x-opencode-client": flags.client,
                 "User-Agent": `opencode/${InstallationVersion}`,
               }
             : {
@@ -443,6 +444,7 @@ export const defaultLayer = Layer.suspend(() =>
     Layer.provide(Config.defaultLayer),
     Layer.provide(Provider.defaultLayer),
     Layer.provide(Plugin.defaultLayer),
+    Layer.provide(RuntimeFlags.defaultLayer),
   ),
 )
 

From 52f9bcbb8204c5bb459ec9227c64dd392a551c41 Mon Sep 17 00:00:00 2001
From: Shoubhit Dash 
Date: Thu, 14 May 2026 00:10:31 +0530
Subject: [PATCH 289/378] refactor(flags): route installation client through
 runtime flags (#27369)

---
 packages/opencode/src/installation/index.ts    |  7 +++++--
 packages/opencode/src/provider/models.ts       |  9 +++++++--
 packages/opencode/test/provider/models.test.ts | 10 ++++++++--
 3 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts
index cc0b06e8e..00fbabe35 100644
--- a/packages/opencode/src/installation/index.ts
+++ b/packages/opencode/src/installation/index.ts
@@ -5,7 +5,6 @@ import { ChildProcess } from "effect/unstable/process"
 import { AppProcess } from "@opencode-ai/core/process"
 import path from "path"
 import { BusEvent } from "@/bus/bus-event"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import * as Log from "@opencode-ai/core/util/log"
 import { makeRuntime } from "@opencode-ai/core/effect/runtime"
 import semver from "semver"
@@ -50,7 +49,11 @@ export const Info = Schema.Struct({
 }).annotate({ identifier: "InstallationInfo" })
 export type Info = Schema.Schema.Type
 
-export const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}`
+export function userAgent(client = "cli") {
+  return `opencode/${InstallationChannel}/${InstallationVersion}/${client}`
+}
+
+export const USER_AGENT = userAgent()
 
 export function isPreview() {
   return InstallationChannel !== "latest"
diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts
index 9f88f9db1..8d88e0fce 100644
--- a/packages/opencode/src/provider/models.ts
+++ b/packages/opencode/src/provider/models.ts
@@ -9,6 +9,7 @@ import { Hash } from "@opencode-ai/core/util/hash"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { withTransientReadRetry } from "@/util/effect-http-client"
 import { CatalogModelStatus } from "./model-status"
+import { RuntimeFlags } from "@/effect/runtime-flags"
 
 const CostTier = Schema.Struct({
   input: Schema.Finite,
@@ -109,11 +110,14 @@ export interface Interface {
 
 export class Service extends Context.Service()("@opencode/ModelsDev") {}
 
-export const layer: Layer.Layer = Layer.effect(
+type Requirements = AppFileSystem.Service | HttpClient.HttpClient | RuntimeFlags.Service
+
+export const layer: Layer.Layer = Layer.effect(
   Service,
   Effect.gen(function* () {
     const fs = yield* AppFileSystem.Service
     const http = HttpClient.filterStatusOk(withTransientReadRetry(yield* HttpClient.HttpClient))
+    const flags = yield* RuntimeFlags.Service
 
     const source = Flag.OPENCODE_MODELS_URL || "https://models.dev"
     const filepath = path.join(
@@ -132,7 +136,7 @@ export const layer: Layer.Layer res.text),
         Effect.timeout("10 seconds"),
@@ -208,6 +212,7 @@ export const layer: Layer.Layer = layer.pipe(
   Layer.provide(FetchHttpClient.layer),
   Layer.provide(AppFileSystem.defaultLayer),
+  Layer.provide(RuntimeFlags.defaultLayer),
 )
 
 export * as ModelsDev from "./models"
diff --git a/packages/opencode/test/provider/models.test.ts b/packages/opencode/test/provider/models.test.ts
index 7ccf126a9..c0dbd5caa 100644
--- a/packages/opencode/test/provider/models.test.ts
+++ b/packages/opencode/test/provider/models.test.ts
@@ -8,6 +8,7 @@ import { ModelsDev } from "../../src/provider/models"
 import { it } from "../lib/effect"
 import { rm, writeFile, utimes, mkdir } from "fs/promises"
 import path from "path"
+import { RuntimeFlags } from "@/effect/runtime-flags"
 
 // test/preload.ts pins OPENCODE_MODELS_PATH to a fixture so other tests can
 // resolve providers without network. These tests need to drive the on-disk
@@ -70,13 +71,16 @@ const fixture2: Record = {
 interface MockState {
   body: string
   status: number
-  calls: Array<{ url: string }>
+  calls: Array<{ url: string; userAgent: string | null }>
 }
 
 const makeMockClient = (state: Ref.Ref) =>
   HttpClient.make((request) =>
     Effect.gen(function* () {
-      yield* Ref.update(state, (s) => ({ ...s, calls: [...s.calls, { url: request.url }] }))
+      yield* Ref.update(state, (s) => ({
+        ...s,
+        calls: [...s.calls, { url: request.url, userAgent: request.headers["user-agent"] ?? null }],
+      }))
       const s = yield* Ref.get(state)
       return HttpClientResponse.fromWeb(request, new Response(s.body, { status: s.status }))
     }),
@@ -89,6 +93,7 @@ const buildLayer = (state: Ref.Ref) =>
   Layer.fresh(ModelsDev.layer).pipe(
     Layer.provide(Layer.succeed(HttpClient.HttpClient, makeMockClient(state))),
     Layer.provide(AppFileSystem.defaultLayer),
+    Layer.provide(RuntimeFlags.layer({ client: "test-client" })),
   )
 
 const writeCache = (data: object, mtimeMs?: number) =>
@@ -202,6 +207,7 @@ describe("ModelsDev Service", () => {
       const final = yield* Ref.get(state)
       expect(final.calls.length).toBe(1)
       expect(final.calls[0].url).toContain("/api.json")
+      expect(final.calls[0].userAgent).toContain("/test-client")
     }),
   )
 

From 22a5e6cc50fa4c9f52014a989bf4b66776eaab85 Mon Sep 17 00:00:00 2001
From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Date: Wed, 13 May 2026 13:45:34 -0500
Subject: [PATCH 290/378] fix(run): restore non-interactive exit behavior
 (#27371)

---
 packages/opencode/src/cli/cmd/run.ts | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 7011b51eb..33dff2ab5 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -24,6 +24,7 @@ import { Filesystem } from "@/util/filesystem"
 import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
 import { Agent } from "@/agent/agent"
 import { Permission } from "@/permission"
+import { FormatError, FormatUnknownError } from "../error"
 import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./run/runtime.stdin"
 
 const runtimeTask = import("./run/runtime")
@@ -82,6 +83,10 @@ function block(info: Inline, output?: string) {
   UI.empty()
 }
 
+function formatRunError(error: unknown) {
+  return FormatError(error) ?? FormatUnknownError(error)
+}
+
 async function tool(part: ToolPart) {
   try {
     const { toolInlineInfo } = await import("./run/tool")
@@ -731,10 +736,13 @@ export const RunCommand = effectCmd({
 
         if (!args.interactive) {
           const events = await client.event.subscribe()
-          const completed = loop(client, events)
+          loop(client, events).catch((e) => {
+            console.error(e)
+            process.exit(1)
+          })
 
           if (args.command) {
-            await client.session.command({
+            const result = await client.session.command({
               sessionID,
               agent,
               model: args.model,
@@ -742,21 +750,25 @@ export const RunCommand = effectCmd({
               arguments: message,
               variant: args.variant,
             })
-            const error = await completed
-            if (error) process.exitCode = 1
+            if (result.error) {
+              if (!emit("error", { error: result.error })) UI.error(formatRunError(result.error))
+              process.exitCode = 1
+            }
             return
           }
 
           const model = pick(args.model)
-          await client.session.prompt({
+          const result = await client.session.prompt({
             sessionID,
             agent,
             model,
             variant: args.variant,
             parts: [...files, { type: "text", text: message }],
           })
-          const error = await completed
-          if (error) process.exitCode = 1
+          if (result.error) {
+            if (!emit("error", { error: result.error })) UI.error(formatRunError(result.error))
+            process.exitCode = 1
+          }
           return
         }
 

From 20cec9155021c37fe557eb17559a35dc0c2b692c Mon Sep 17 00:00:00 2001
From: Shoubhit Dash 
Date: Thu, 14 May 2026 00:38:38 +0530
Subject: [PATCH 291/378] fix(provider): restore model suggestions (#27372)

---
 packages/opencode/src/provider/provider.ts    | 53 ++++++++++++++++---
 .../opencode/test/provider/provider.test.ts   | 27 ++++++++++
 2 files changed, 73 insertions(+), 7 deletions(-)

diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index ca87c40b7..0f757caf1 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -979,6 +979,7 @@ export interface Interface {
 interface State {
   models: Map
   providers: Record
+  catalog: Record
   sdk: Map
   modelLoaders: Record
   varsLoaders: Record
@@ -1104,6 +1105,38 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
   }
 }
 
+function suggestionModelIDs(provider: Info | undefined) {
+  if (!provider) return []
+  return Object.keys(provider.models).filter((id) => {
+    const model = provider.models[id]
+    if (model.status === "deprecated") return false
+    if (model.status === "alpha" && !Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) return false
+    return true
+  })
+}
+
+function modelSuggestions(provider: Info | undefined, modelID: ModelID) {
+  const available = suggestionModelIDs(provider)
+  const fuzzy = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 }).map((m) => m.target)
+  if (fuzzy.length) return fuzzy
+  const query = modelID
+    .toLowerCase()
+    .split(/[^a-z0-9]+/)
+    .filter((part) => part.length > 1)
+  return sortBy(
+    available
+      .map((id) => ({
+        id,
+        score: query.filter((part) => id.toLowerCase().includes(part)).length,
+      }))
+      .filter((item) => item.score > 0),
+    [(item) => item.score, "desc"],
+    [(item) => item.id, "asc"],
+  )
+    .slice(0, 3)
+    .map((item) => item.id)
+}
+
 const layer = Layer.effect(
   Service,
   Effect.gen(function* () {
@@ -1120,7 +1153,8 @@ const layer = Layer.effect(
         const bridge = yield* EffectBridge.make()
         const cfg = yield* config.get()
         const modelsDev = yield* modelsDevSvc.get()
-        const database = mapValues(modelsDev, fromModelsDevProvider)
+        const catalog = mapValues(modelsDev, fromModelsDevProvider)
+        const database = mapValues(catalog, toPublicInfo)
 
         const providers: Record = {} as Record
         const languages = new Map()
@@ -1437,6 +1471,7 @@ const layer = Layer.effect(
         return {
           models: languages,
           providers,
+          catalog,
           sdk,
           modelLoaders,
           varsLoaders,
@@ -1597,16 +1632,20 @@ const layer = Layer.effect(
       const s = yield* InstanceState.get(state)
       const provider = s.providers[providerID]
       if (!provider) {
-        const available = Object.keys(s.providers)
-        const matches = fuzzysort.go(providerID, available, { limit: 3, threshold: -10000 })
-        throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
+        const catalogProvider = s.catalog[providerID]
+        const suggestions = catalogProvider
+          ? modelSuggestions(catalogProvider, modelID)
+          : fuzzysort
+              .go(providerID, Object.keys({ ...s.catalog, ...s.providers }), { limit: 3, threshold: -10000 })
+              .map((m) => m.target)
+        throw new ModelNotFoundError({ providerID, modelID, suggestions })
       }
 
       const info = provider.models[modelID]
       if (!info) {
-        const available = Object.keys(provider.models)
-        const matches = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 })
-        throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
+        const current = modelSuggestions(provider, modelID)
+        const suggestions = current.length ? current : modelSuggestions(s.catalog[providerID], modelID)
+        throw new ModelNotFoundError({ providerID, modelID, suggestions })
       }
       return info
     })
diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts
index 2270418be..ab39c1902 100644
--- a/packages/opencode/test/provider/provider.test.ts
+++ b/packages/opencode/test/provider/provider.test.ts
@@ -19,6 +19,7 @@ import { testEffect } from "../lib/effect"
 
 const env = makeRuntime(Env.Service, Env.defaultLayer)
 const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v))
+const remove = (k: string) => env.runSync((svc) => svc.remove(k))
 
 async function run(fn: (provider: Provider.Interface) => Effect.Effect) {
   return AppRuntime.runPromise(
@@ -1604,6 +1605,32 @@ test("ModelNotFoundError for provider includes suggestions", async () => {
   })
 })
 
+test("ModelNotFoundError suggests catalog models for unloaded providers", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+        }),
+      )
+    },
+  })
+  await WithInstance.provide({
+    directory: tmp.path,
+    fn: async () => {
+      remove("OPENCODE_API_KEY")
+      try {
+        await getModel(ProviderID.opencode, ModelID.make("claude-haiku-fake-model"))
+        throw new Error("expected model lookup to fail")
+      } catch (e) {
+        if (!Provider.ModelNotFoundError.isInstance(e)) throw e
+        expect(e.data.suggestions).toContain("claude-haiku-4-5")
+      }
+    },
+  })
+})
+
 test("getProvider returns undefined for nonexistent provider", async () => {
   await using tmp = await tmpdir({
     init: async (dir) => {

From 9ee1f6ceba8964c7db34fb9429533f67269ef089 Mon Sep 17 00:00:00 2001
From: Shoubhit Dash 
Date: Thu, 14 May 2026 01:02:07 +0530
Subject: [PATCH 292/378] fix(server): map busy sessions in http handlers
 (#27375)

---
 .../routes/instance/httpapi/handlers/session.ts  | 16 ++++++++++++----
 .../routes/instance/httpapi/middleware/error.ts  |  9 ---------
 2 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
index b12be2cfc..df2b40de5 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
@@ -52,6 +52,14 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
     const bus = yield* Bus.Service
     const scope = yield* Scope.Scope
 
+    const mapBusy = (effect: Effect.Effect): Effect.Effect =>
+      effect.pipe(
+        Effect.catchCause((cause): Effect.Effect => {
+          if (Cause.squash(cause) instanceof Session.BusyError) return Effect.fail(new HttpApiError.BadRequest({}))
+          return Effect.failCause(cause)
+        }),
+      )
+
     const list = Effect.fn("SessionHttpApi.list")(function* (ctx: { query: typeof ListQuery.Type }) {
       return yield* session.list({
         directory: ctx.query.scope === "project" ? undefined : ctx.query.directory,
@@ -329,7 +337,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       payload: typeof ShellPayload.Type
     }) {
       yield* requireSession(ctx.params.sessionID)
-      return yield* promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID })
+      return yield* mapBusy(promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID }))
     })
 
     const revert = Effect.fn("SessionHttpApi.revert")(function* (ctx: {
@@ -337,12 +345,12 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       payload: typeof RevertPayload.Type
     }) {
       yield* requireSession(ctx.params.sessionID)
-      return yield* revertSvc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload })
+      return yield* mapBusy(revertSvc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload }))
     })
 
     const unrevert = Effect.fn("SessionHttpApi.unrevert")(function* (ctx: { params: { sessionID: SessionID } }) {
       yield* requireSession(ctx.params.sessionID)
-      return yield* revertSvc.unrevert({ sessionID: ctx.params.sessionID })
+      return yield* mapBusy(revertSvc.unrevert({ sessionID: ctx.params.sessionID }))
     })
 
     const permissionRespond = Effect.fn("SessionHttpApi.permissionRespond")(function* (ctx: {
@@ -358,7 +366,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       params: { sessionID: SessionID; messageID: MessageID }
     }) {
       yield* requireSession(ctx.params.sessionID)
-      yield* runState.assertNotBusy(ctx.params.sessionID)
+      yield* mapBusy(runState.assertNotBusy(ctx.params.sessionID))
       yield* session.removeMessage(ctx.params)
       return true
     })
diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts
index bb75f6602..589641037 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts
@@ -1,5 +1,4 @@
 import { Provider } from "@/provider/provider"
-import { Session } from "@/session/session"
 import { iife } from "@/util/iife"
 import { NamedError } from "@opencode-ai/core/util/error"
 import * as Log from "@opencode-ai/core/util/log"
@@ -33,14 +32,6 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect)
           }),
         )
       }
-      if (error instanceof Session.BusyError) {
-        return Effect.succeed(
-          HttpServerResponse.jsonUnsafe(new NamedError.Unknown({ message: error.message }).toObject(), {
-            status: 400,
-          }),
-        )
-      }
-
       return Effect.succeed(
         HttpServerResponse.jsonUnsafe(
           new NamedError.Unknown({

From c197fd92b7f41919f5b5f249a830ca7bfea603a7 Mon Sep 17 00:00:00 2001
From: Frank 
Date: Wed, 13 May 2026 15:58:32 -0400
Subject: [PATCH 293/378] sync

---
 packages/console/function/src/stat.ts | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/packages/console/function/src/stat.ts b/packages/console/function/src/stat.ts
index 54d38bc31..957adf491 100644
--- a/packages/console/function/src/stat.ts
+++ b/packages/console/function/src/stat.ts
@@ -1,7 +1,7 @@
 import { and, Database, inArray } from "@opencode-ai/console-core/drizzle/index.js"
 import { ModelTpsRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
 
-type Result = Record>
+type Result = Record
 
 export default {
   async fetch(request: Request) {
@@ -9,7 +9,6 @@ export default {
 
     const body = (await request.json()) as { ids: string[] }
     const ids = body.ids
-
     if (ids.length === 0) return Response.json({} satisfies Result)
 
     const toInterval = (date: Date) =>
@@ -26,18 +25,19 @@ export default {
       tx
         .select()
         .from(ModelTpsRateLimitTable)
-        .where(and(inArray(ModelTpsRateLimitTable.id, body.ids), inArray(ModelTpsRateLimitTable.interval, intervals))),
+        .where(and(inArray(ModelTpsRateLimitTable.id, ids), inArray(ModelTpsRateLimitTable.interval, intervals))),
     )
 
+    const rowsByKey = new Map(rows.map((row) => [`${row.id}:${row.interval}`, row]))
     const result: Result = Object.fromEntries(
-      body.ids.map((id) => [
+      ids.map((id) => [
         id,
-        Object.fromEntries(intervals.map((interval) => [interval, { qualify: 0, unqualify: 0 }])),
+        intervals.map((interval) => {
+          const row = rowsByKey.get(`${id}:${interval}`)
+          return { interval, qualify: row?.qualify ?? 0, unqualify: row?.unqualify ?? 0 }
+        }),
       ]),
     )
-    for (const row of rows) {
-      result[row.id][row.interval] = { qualify: row.qualify, unqualify: row.unqualify }
-    }
     return Response.json(result)
   },
 }

From 3b7a5e783d59e8986dca6e5df48663613fa80722 Mon Sep 17 00:00:00 2001
From: Sebastian 
Date: Wed, 13 May 2026 23:00:48 +0200
Subject: [PATCH 294/378] fix keymap fallback priority and TUI config
 diagnostics (#27384)

---
 bun.lock                                      | 30 +++++------
 package.json                                  |  6 +--
 packages/core/src/util/log.ts                 |  6 ++-
 .../opencode/src/cli/cmd/tui/config/tui.ts    | 52 ++++++++++++++-----
 packages/opencode/test/config/tui.test.ts     | 19 +++++++
 packages/opencode/test/util/log.test.ts       | 28 ++++++++++
 packages/plugin/package.json                  |  6 +--
 7 files changed, 111 insertions(+), 36 deletions(-)

diff --git a/bun.lock b/bun.lock
index 2a79552b9..dc715b527 100644
--- a/bun.lock
+++ b/bun.lock
@@ -536,9 +536,9 @@
         "typescript": "catalog:",
       },
       "peerDependencies": {
-        "@opentui/core": ">=0.2.8",
-        "@opentui/keymap": ">=0.2.8",
-        "@opentui/solid": ">=0.2.8",
+        "@opentui/core": ">=0.2.9",
+        "@opentui/keymap": ">=0.2.9",
+        "@opentui/solid": ">=0.2.9",
       },
       "optionalPeers": [
         "@opentui/core",
@@ -721,9 +721,9 @@
     "@npmcli/arborist": "9.4.0",
     "@octokit/rest": "22.0.0",
     "@openauthjs/openauth": "0.0.0-20250322224806",
-    "@opentui/core": "0.2.8",
-    "@opentui/keymap": "0.2.8",
-    "@opentui/solid": "0.2.8",
+    "@opentui/core": "0.2.9",
+    "@opentui/keymap": "0.2.9",
+    "@opentui/solid": "0.2.9",
     "@pierre/diffs": "1.1.0-beta.18",
     "@playwright/test": "1.59.1",
     "@sentry/solid": "10.36.0",
@@ -1590,23 +1590,23 @@
 
     "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
 
-    "@opentui/core": ["@opentui/core@0.2.8", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.8", "@opentui/core-darwin-x64": "0.2.8", "@opentui/core-linux-arm64": "0.2.8", "@opentui/core-linux-x64": "0.2.8", "@opentui/core-win32-arm64": "0.2.8", "@opentui/core-win32-x64": "0.2.8" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-bRRiCXuwjS8/6mN1oA5iVaf55z9APyalm7FnoxkLkEyIU1VDaQeTpYtElBbfo1rxtcO6Rj53XywH9oW8auNO9A=="],
+    "@opentui/core": ["@opentui/core@0.2.9", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.9", "@opentui/core-darwin-x64": "0.2.9", "@opentui/core-linux-arm64": "0.2.9", "@opentui/core-linux-x64": "0.2.9", "@opentui/core-win32-arm64": "0.2.9", "@opentui/core-win32-x64": "0.2.9" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-Kmeqi+yiDau+P45xDeX08GS50FK917qVwuPTN7HGxsQ9Byt7Iifq/6OMiSnFULBzoZtECdKLgQF1XwLsNm1wig=="],
 
-    "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Qh6VCMQgW3hWh/7MR51y+XuQezh8NOLwKS8EQSoKzAr4VOc/W5P0/DvgMKgwaqXw2Mz0AIba/BvZ6by20yc4zA=="],
+    "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-D2ne8Xgyrg71L/9lF7vPh30Sxz6+3yAqpT0m87WiI+040J7sQEyK3YM/7w5JKuVemQ4H54HSPjofrUHjfibjoQ=="],
 
-    "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-wQjJ38C3IiVx/gwwBYxnCarzgD75FdS7IyUErt3lhn57XriNiCbb7ScphWnRMwwtL8CI+bBGzClroDRA2lCfvg=="],
+    "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ymbbt/wN/vgB8g+kbHospJclVKHq6cdgfEYg9qgsSHp2vqMFBqlQQ692MS3BcZfX9jrKROK7NvC6Hj37X5K/7Q=="],
 
-    "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-fx4ADeWSSSVU1O/MkMnklCRxtWRy6CLeAvktLlNdPb+BhmQIDg1kpZcdv7m/3cgD1/ksFEXIwO6VTvfKYE0umw=="],
+    "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-0RIrVe4+42oELHtSJBaaYhngUeMKwSeqfdtKeSwEFwCzrqrNXxCpXQdOo8QvjOKGgng4Smn6O6KM8sgCj4SSPQ=="],
 
-    "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.8", "", { "os": "linux", "cpu": "x64" }, "sha512-4ekUyzopBj2ClsUbneLnUOrmZtvU67FCVFLgmBfKL4IvVl/P0YobGNg71gN1JNiYpY7hK77qOpidVLHcNMIE7w=="],
+    "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.9", "", { "os": "linux", "cpu": "x64" }, "sha512-fjCZP1IOLWm68FYl2PRzFg1vfu226FPfiJsdNtLbhaYF2uEZOB/v1BQph21OKnB7GC7X8GQatvhM5sS3DQ2MSQ=="],
 
-    "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-63K046wpzTzQOLOG9LTsp3+Ld0TNTxeQczexkg0pKSBxZFhws+/9YIGjTctZmJUfE1g1X4tI31dO+KNRpXRHQw=="],
+    "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-u8SP3u2QEJqcGIULYZ7Lkht9ss7wcN4/LnMuqt9rPOiCduFn/VW4r8lQCftZ6DRSqyoP9mJ1xLzOSFl98UYyEw=="],
 
-    "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.8", "", { "os": "win32", "cpu": "x64" }, "sha512-+WDiTlTyDpgkis8rPAhW1fS7TwXJih+fk+RYXS2bC3tAKsRD+O3PRSkVABRbjkuXbtfJZf2cjOHZFGN4Vf5qDg=="],
+    "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.9", "", { "os": "win32", "cpu": "x64" }, "sha512-un7iSy9XHLwa6ouVpUj3eEGnXfPG50OMUJ2Dt30Jvn2vhNwIU2VO4RGx06l5OUD6GGVpHb0RqmG/384oo9i+HA=="],
 
-    "@opentui/keymap": ["@opentui/keymap@0.2.8", "", { "dependencies": { "@opentui/core": "0.2.8" }, "peerDependencies": { "@opentui/react": "0.2.8", "@opentui/solid": "0.2.8", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-/H9j8fP64cf3/nFDCvVP8+7cwU/oRh4sgfQH2NhcPp8illgBb/e9pG5x3vM0nK4RVyTqUvkPXsOeIX5u7vltlg=="],
+    "@opentui/keymap": ["@opentui/keymap@0.2.9", "", { "dependencies": { "@opentui/core": "0.2.9" }, "peerDependencies": { "@opentui/react": "0.2.9", "@opentui/solid": "0.2.9", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-yCc6L0Jqa8aVaNAVniTV5bNygJayUE6mxWfaBQY5VV5QwsZemXSeQQc4vP2eetH4Rrm1gGA59gLP+zh6+s5fvw=="],
 
-    "@opentui/solid": ["@opentui/solid@0.2.8", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.8", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-f2g0riBuzk4/ZmcJnp1k13odUmNZcfA3nF7RzdSlEfpkwNDfc4xqnRAwYbNNDwGNrJX0JDCTEZY5ZEhuL155MQ=="],
+    "@opentui/solid": ["@opentui/solid@0.2.9", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.9", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-qpNSCELxRvBAx8Zneqz46FYYTvJNFjDvhqzAAZRNoaHathfU6X6iPxWMUqP/9ls5VcHFW1TDJdgtpsq1N/nHMQ=="],
 
     "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
 
diff --git a/package.json b/package.json
index f1cc7da5c..50406e1f4 100644
--- a/package.json
+++ b/package.json
@@ -35,9 +35,9 @@
       "@types/cross-spawn": "6.0.6",
       "@octokit/rest": "22.0.0",
       "@hono/zod-validator": "0.4.2",
-      "@opentui/core": "0.2.8",
-      "@opentui/keymap": "0.2.8",
-      "@opentui/solid": "0.2.8",
+      "@opentui/core": "0.2.9",
+      "@opentui/keymap": "0.2.9",
+      "@opentui/solid": "0.2.9",
       "ulid": "3.0.1",
       "@kobalte/core": "0.13.11",
       "@types/luxon": "3.7.1",
diff --git a/packages/core/src/util/log.ts b/packages/core/src/util/log.ts
index 83060b29c..3b5249cdc 100644
--- a/packages/core/src/util/log.ts
+++ b/packages/core/src/util/log.ts
@@ -20,6 +20,7 @@ const levelPriority: Record = {
   ERROR: 3,
 }
 const keep = 10
+const initializedRunID = "OPENCODE_LOG_INITIALIZED_RUN_ID"
 
 let level: Level = "INFO"
 
@@ -70,7 +71,10 @@ export async function init(options: Options) {
     Global.Path.log,
     options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
   )
-  await fs.truncate(logpath).catch(() => {})
+  const runID = process.env.OPENCODE_RUN_ID
+  const shouldTruncate = !options.dev || !runID || process.env[initializedRunID] !== runID
+  if (shouldTruncate) await fs.truncate(logpath).catch(() => {})
+  if (options.dev && runID) process.env[initializedRunID] = runID
   const stream = createWriteStream(logpath, { flags: "a" })
   write = async (msg: any) => {
     return new Promise((resolve, reject) => {
diff --git a/packages/opencode/src/cli/cmd/tui/config/tui.ts b/packages/opencode/src/cli/cmd/tui/config/tui.ts
index 562b369db..0d4be41df 100644
--- a/packages/opencode/src/cli/cmd/tui/config/tui.ts
+++ b/packages/opencode/src/cli/cmd/tui/config/tui.ts
@@ -3,9 +3,8 @@ export * as TuiConfig from "./tui"
 import path from "path"
 import { createBindingLookup } from "@opentui/keymap/extras"
 import { mergeDeep, unique } from "remeda"
-import { Context, Effect, Fiber, Layer, Schema } from "effect"
+import { Cause, Context, Effect, Fiber, Layer, Schema } from "effect"
 import { ConfigParse } from "@/config/parse"
-import { InvalidError } from "@/config/error"
 import * as ConfigPaths from "@/config/paths"
 import { migrateTuiConfig } from "./tui-migrate"
 import { KeymapLeaderTimeoutDefault, resolveAttentionSoundPaths, TuiInfo } from "./tui-schema"
@@ -24,6 +23,7 @@ import { ConfigVariable } from "@/config/variable"
 import { Npm } from "@opencode-ai/core/npm"
 import type { DeepMutable } from "@opencode-ai/core/schema"
 import type { TuiAttentionSoundName } from "@opencode-ai/plugin/tui"
+import { FormatError, FormatUnknownError } from "@/cli/error"
 
 const log = Log.create({ service: "tui.config" })
 
@@ -79,8 +79,26 @@ function normalize(raw: Record) {
   }
 }
 
+function dropUnknownKeybinds(input: Record, configFilepath: string) {
+  if (!isRecord(input.keybinds)) return input
+
+  const invalid = TuiKeybind.unknownKeys(input.keybinds)
+  if (!invalid.length) return input
+
+  log.warn("ignored unknown tui keybinds", {
+    path: configFilepath,
+    keybinds: invalid,
+    hint: "Remove these entries or rename them to keys from the tui.json schema.",
+  })
+  return {
+    ...input,
+    keybinds: Object.fromEntries(Object.entries(input.keybinds).filter(([key]) => !invalid.includes(key))),
+  }
+}
+
 const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: string }) {
   const afs = yield* AppFileSystem.Service
+  let appliedOrder = 0
 
   const resolvePlugins = (config: Info, configFilepath: string): Effect.Effect =>
     Effect.gen(function* () {
@@ -101,16 +119,7 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
       if (!isRecord(data)) return {} as Info
       // Flatten a nested "tui" key so users who wrote `{ "tui": { ... } }` inside tui.json
       // (mirroring the old opencode.json shape) still get their settings applied.
-      const normalized = normalize(data)
-      if (isRecord(normalized.keybinds)) {
-        const invalid = TuiKeybind.unknownKeys(normalized.keybinds)
-        if (invalid.length) {
-          throw new InvalidError({
-            path: configFilepath,
-            message: `Unrecognized keybind${invalid.length === 1 ? "" : "s"}: ${invalid.join(", ")}`,
-          })
-        }
-      }
+      const normalized = dropUnknownKeybinds(normalize(data), configFilepath)
       const parsed = ConfigParse.schema(Info, normalized, configFilepath)
       const validated = parsed.attention?.sounds
         ? {
@@ -127,7 +136,12 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
       // can sync-throw — those become defects, which orElseSucceed wouldn't catch.
       Effect.catchCause((cause) =>
         Effect.sync(() => {
-          log.warn("invalid tui config", { path: configFilepath, cause })
+          const error = Cause.squash(cause)
+          const reason = FormatError(error) ?? FormatUnknownError(error)
+          log.warn("skipping invalid tui config", {
+            path: configFilepath,
+            reason,
+          })
           return {} as Info
         }),
       ),
@@ -141,18 +155,28 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
       const text = yield* afs.readFileStringSafe(filepath).pipe(
         Effect.catchCause((cause) =>
           Effect.sync(() => {
-            log.warn("failed to read tui config", { path: filepath, cause })
+            const error = Cause.squash(cause)
+            const reason = FormatError(error) ?? FormatUnknownError(error)
+            log.warn("failed to read tui config", {
+              path: filepath,
+              reason,
+            })
             return undefined
           }),
         ),
       )
       if (!text) return {} as Info
+      log.info("loading tui config", { path: filepath })
       return yield* load(text, filepath)
     })
 
   const mergeFile = (acc: Acc, file: string) =>
     Effect.gen(function* () {
       const data = yield* loadFile(file)
+      if (Object.keys(data).length) {
+        appliedOrder += 1
+        log.info("applying tui config", { path: file, order: appliedOrder })
+      }
       acc.result = mergeDeep(acc.result, data)
       if (!data.plugin?.length) return
 
diff --git a/packages/opencode/test/config/tui.test.ts b/packages/opencode/test/config/tui.test.ts
index 30c5a65fb..4eb96b957 100644
--- a/packages/opencode/test/config/tui.test.ts
+++ b/packages/opencode/test/config/tui.test.ts
@@ -439,6 +439,25 @@ it.instance("merges keybind overrides across precedence layers", () =>
   ),
 )
 
+it.instance("ignores unknown keybind names without dropping valid overrides from the same file", () =>
+  withCleanState(
+    Effect.gen(function* () {
+      const fs = yield* AppFileSystem.Service
+      const test = yield* TestInstance
+      yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), {
+        keybinds: {
+          session_delete: "ctrl+d",
+          not_a_real_keybind: "ctrl+q",
+        },
+      })
+
+      const config = yield* getTuiConfig(test.directory)
+      expect(config.keybinds.get("session.delete")?.[0]?.key).toBe("ctrl+d")
+      expect(config.keybinds.get("not_a_real_keybind")).toEqual([])
+    }),
+  ),
+)
+
 it.instance("resolves keybind lookup from canonical keybinds", () =>
   withCleanState(
     Effect.gen(function* () {
diff --git a/packages/opencode/test/util/log.test.ts b/packages/opencode/test/util/log.test.ts
index defd8c981..62dc1d61c 100644
--- a/packages/opencode/test/util/log.test.ts
+++ b/packages/opencode/test/util/log.test.ts
@@ -47,3 +47,31 @@ it.live("init cleanup keeps the newest timestamped logs", () =>
     expect(next).toContain(list.at(-1)!)
   }),
 )
+
+it.live("local dev log is not truncated twice for the same run", () =>
+  Effect.gen(function* () {
+    const log = Global.Path.log
+    const runID = process.env.OPENCODE_RUN_ID
+    const initialized = process.env.OPENCODE_LOG_INITIALIZED_RUN_ID
+    yield* Effect.addFinalizer(() =>
+      Effect.sync(() => {
+        Global.Path.log = log
+        if (runID === undefined) delete process.env.OPENCODE_RUN_ID
+        else process.env.OPENCODE_RUN_ID = runID
+        if (initialized === undefined) delete process.env.OPENCODE_LOG_INITIALIZED_RUN_ID
+        else process.env.OPENCODE_LOG_INITIALIZED_RUN_ID = initialized
+      }),
+    )
+
+    const dir = yield* tmpdirScoped()
+    Global.Path.log = dir
+    process.env.OPENCODE_RUN_ID = "run-1"
+    delete process.env.OPENCODE_LOG_INITIALIZED_RUN_ID
+
+    yield* Effect.promise(() => Log.init({ print: false, dev: true }))
+    yield* Effect.promise(() => fs.writeFile(path.join(dir, "dev.log"), "main startup\n"))
+    yield* Effect.promise(() => Log.init({ print: false, dev: true }))
+
+    expect(yield* Effect.promise(() => fs.readFile(path.join(dir, "dev.log"), "utf8"))).toContain("main startup")
+  }),
+)
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index baff4cddb..82fdc0ab8 100644
--- a/packages/plugin/package.json
+++ b/packages/plugin/package.json
@@ -22,9 +22,9 @@
     "zod": "catalog:"
   },
   "peerDependencies": {
-    "@opentui/core": ">=0.2.8",
-    "@opentui/keymap": ">=0.2.8",
-    "@opentui/solid": ">=0.2.8"
+    "@opentui/core": ">=0.2.9",
+    "@opentui/keymap": ">=0.2.9",
+    "@opentui/solid": ">=0.2.9"
   },
   "peerDependenciesMeta": {
     "@opentui/core": {

From 0d074492dfaaa90db68cfc9593cf5a48e74848e2 Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]" 
Date: Wed, 13 May 2026 21:20:10 +0000
Subject: [PATCH 295/378] 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 876b96960..73a76a1d5 100644
--- a/nix/hashes.json
+++ b/nix/hashes.json
@@ -1,8 +1,8 @@
 {
   "nodeModules": {
-    "x86_64-linux": "sha256-cRhvzZoW6gBbE0sQm1+e+6/WgajuA6MSIL5iroFsfqs=",
-    "aarch64-linux": "sha256-0knZfxBULqkt5u6sXFx+a/vqw2rc6IC1+LeAd4TNFhM=",
-    "aarch64-darwin": "sha256-jL4tO+EHSmUF+gQGEaLzAbTxxjkL8OyhTk13vsbomgM=",
-    "x86_64-darwin": "sha256-bsa7IpS3GaxagcigTa0yqZTkf4e/nbcTQ9aZeb+5eHQ="
+    "x86_64-linux": "sha256-QIj9PhOXR/GngV/dPjCF7n5rKro2fcTNGzJ47a41Z2Q=",
+    "aarch64-linux": "sha256-fQl7BjjTYtRKT3HRVhubaIVww/puUFSTzVV5bTy8II8=",
+    "aarch64-darwin": "sha256-81IAmdjiYZz8IgMJt0+VxzdOS80gTHc5SendwEW/vD4=",
+    "x86_64-darwin": "sha256-5OMX4VVBMfEmkYvzd09oksAt5hKkxDs84miO804LBI8="
   }
 }

From 0d8c9f343735d3b942e5921a83d764529f64089a Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 17:46:47 -0400
Subject: [PATCH 296/378] docs: add LayerMap example (#27388)

---
 packages/core/src/plugin/layer-map.example.ts | 94 +++++++++++++++++++
 1 file changed, 94 insertions(+)
 create mode 100644 packages/core/src/plugin/layer-map.example.ts

diff --git a/packages/core/src/plugin/layer-map.example.ts b/packages/core/src/plugin/layer-map.example.ts
new file mode 100644
index 000000000..63e33d3f0
--- /dev/null
+++ b/packages/core/src/plugin/layer-map.example.ts
@@ -0,0 +1,94 @@
+export * as LayerMapExample from "./layer-map.example"
+
+import { Context, Effect, Layer, LayerMap } from "effect"
+import { Npm } from "../npm"
+
+/**
+ * Tutorial: split global services from context-specific services.
+ *
+ * Use this pattern when part of the app should be constructed once at the app edge,
+ * while another part should be cached per request/project/workspace key.
+ *
+ * In this example:
+ * - Npm.Service is the global service. It is not keyed by request context and should
+ *   be provided once by the application runtime.
+ * - ConfigService is context-specific. It is built from a RequestContext key and is
+ *   cached by LayerMap for that key.
+ * - ConfigServiceMap.layer owns the cache. Provide it once globally, then each
+ *   request can provide ConfigServiceMap.get(context) to select the right instance.
+ *
+ * Lifetime model:
+ * - ConfigServiceMap.layer has the app/global lifetime and depends on Npm.Service.
+ * - ConfigServiceMap.get(context) has the request/context lifetime and provides
+ *   ConfigService for exactly that context key.
+ * - The cached ConfigService entry stays alive while something is using it. Once idle,
+ *   it remains cached for idleTimeToLive, then its scope is finalized.
+ * - invalidate(context) removes the cache entry for future lookups. Active users keep
+ *   running on the old instance; the next lookup can create a fresh instance.
+ *
+ * Key model:
+ * - Keys can be strings, structs, classes, arrays, etc.
+ * - Prefer primitive or immutable keys. Effect uses Hash / Equal semantics for cache
+ *   lookup, so mutating an object after it has been used as a key is a bug.
+ */
+
+export type RequestContext = {
+  readonly directory: string
+  readonly workspace: string
+}
+
+export class RequestContextRef extends Context.Service()(
+  "@opencode/example/RequestContextRef",
+) {}
+
+export interface ConfigServiceShape {
+  readonly directory: string
+  readonly workspace: string
+  readonly nextUse: () => Effect.Effect
+  readonly which: Npm.Interface["which"]
+}
+
+export class ConfigService extends Context.Service()(
+  "@opencode/example/ConfigService",
+) {}
+
+const configServiceLayer = Layer.effect(
+  ConfigService,
+  Effect.gen(function* () {
+    const context = yield* RequestContextRef
+    const npm = yield* Npm.Service
+
+    let useCount = 0
+
+    return ConfigService.of({
+      directory: context.directory,
+      workspace: context.workspace,
+      nextUse: () => Effect.succeed(++useCount),
+      which: npm.which,
+    })
+  }),
+)
+
+export class ConfigServiceMap extends LayerMap.Service()("@opencode/example/ConfigServiceMap", {
+  lookup: (context: RequestContext) =>
+    configServiceLayer.pipe(Layer.provide(Layer.succeed(RequestContextRef, RequestContextRef.of(context)))),
+  idleTimeToLive: "5 minutes",
+}) {}
+
+export const appLayer = ConfigServiceMap.layer
+
+export const readConfig = Effect.fn("LayerMapExample.readConfig")(function* () {
+  const config = yield* ConfigService
+
+  return {
+    directory: config.directory,
+    workspace: config.workspace,
+    useCount: yield* config.nextUse(),
+  }
+})
+
+export const handleRequest = Effect.fn("LayerMapExample.handleRequest")(function* (context: RequestContext) {
+  return yield* readConfig().pipe(Effect.provide(ConfigServiceMap.get(context)))
+})
+
+export const invalidateContext = (context: RequestContext) => ConfigServiceMap.invalidate(context)

From 44b432c3fde168bd5fb43ee1fc9dd50b29ed71ea Mon Sep 17 00:00:00 2001
From: Frank 
Date: Wed, 13 May 2026 18:05:04 -0400
Subject: [PATCH 297/378] sync

---
 packages/console/app/src/routes/zen/util/handler.ts | 2 +-
 1 file changed, 1 insertion(+), 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 d9e564169..3af36ad77 100644
--- a/packages/console/app/src/routes/zen/util/handler.ts
+++ b/packages/console/app/src/routes/zen/util/handler.ts
@@ -216,7 +216,7 @@ export async function handler(
         // ie. 400 error is usually provider error like malformed request
         res.status !== 400 &&
         // ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
-        res.status !== 404 &&
+        !(modelInfo.id.startsWith("gpt-") && res.status === 404) &&
         // ie. cannot change codex model providers mid-session
         modelInfo.stickyProvider !== "strict" &&
         modelInfo.fallbackProvider &&

From 73e1de4513d082c07b8ed3ffcb5f2463d3a3aaed Mon Sep 17 00:00:00 2001
From: opencode 
Date: Wed, 13 May 2026 22:18:40 +0000
Subject: [PATCH 298/378] sync release versions for v1.14.49

---
 bun.lock                               | 34 +++++++++++++-------------
 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/core/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/http-recorder/package.json    |  2 +-
 packages/llm/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/web/package.json              |  2 +-
 sdks/vscode/package.json               |  2 +-
 20 files changed, 41 insertions(+), 41 deletions(-)

diff --git a/bun.lock b/bun.lock
index dc715b527..3fafe9d5e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -29,7 +29,7 @@
     },
     "packages/app": {
       "name": "@opencode-ai/app",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/core": "workspace:*",
@@ -84,7 +84,7 @@
     },
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
@@ -119,7 +119,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -146,7 +146,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@ai-sdk/anthropic": "3.0.64",
         "@ai-sdk/openai": "3.0.48",
@@ -168,7 +168,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -192,7 +192,7 @@
     },
     "packages/core": {
       "name": "@opencode-ai/core",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -253,7 +253,7 @@
     },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "drizzle-orm": "catalog:",
         "effect": "catalog:",
@@ -307,7 +307,7 @@
     },
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@opencode-ai/core": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
@@ -337,7 +337,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "catalog:",
@@ -353,7 +353,7 @@
     },
     "packages/http-recorder": {
       "name": "@opencode-ai/http-recorder",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@effect/platform-node": "catalog:",
         "effect": "catalog:",
@@ -366,7 +366,7 @@
     },
     "packages/llm": {
       "name": "@opencode-ai/llm",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@smithy/eventstream-codec": "4.2.14",
         "@smithy/util-utf8": "4.2.2",
@@ -384,7 +384,7 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -520,7 +520,7 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "effect": "catalog:",
@@ -558,7 +558,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "cross-spawn": "catalog:",
       },
@@ -573,7 +573,7 @@
     },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -608,7 +608,7 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/core": "workspace:*",
@@ -657,7 +657,7 @@
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.14.48",
+      "version": "1.14.49",
       "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 86999ed45..20d417b01 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/app",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "description": "",
   "type": "module",
   "exports": {
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index 7e736ca77..1ae0956c1 100644
--- a/packages/console/app/package.json
+++ b/packages/console/app/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-app",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "type": "module",
   "license": "MIT",
   "scripts": {
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index 986103ece..3eaac7913 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.14.48",
+  "version": "1.14.49",
   "private": true,
   "type": "module",
   "license": "MIT",
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index 5c1d1ba22..7c05c1e19 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-function",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "$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 6c101f051..4f3a61cbd 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-mail",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",
diff --git a/packages/core/package.json b/packages/core/package.json
index 4c47fea8b..0551957f0 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "name": "@opencode-ai/core",
   "type": "module",
   "license": "MIT",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index 0dfcd4544..d9d7aa0bd 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop",
   "private": true,
-  "version": "1.14.48",
+  "version": "1.14.49",
   "type": "module",
   "license": "MIT",
   "homepage": "https://opencode.ai",
diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json
index 88e5406cb..846f57093 100644
--- a/packages/enterprise/package.json
+++ b/packages/enterprise/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/enterprise",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "private": true,
   "type": "module",
   "license": "MIT",
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index 9b77cf8b9..e4ac34b07 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.14.48"
+version = "1.14.49"
 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.14.48/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.49/opencode-darwin-arm64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.48/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.49/opencode-darwin-x64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.48/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.49/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.14.48/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.49/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.14.48/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.49/opencode-windows-x64.zip"
 cmd = "./opencode.exe"
 args = ["acp"]
diff --git a/packages/function/package.json b/packages/function/package.json
index b644ca7df..107d3eca7 100644
--- a/packages/function/package.json
+++ b/packages/function/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/function",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",
diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json
index 18ea8b175..807d248c4 100644
--- a/packages/http-recorder/package.json
+++ b/packages/http-recorder/package.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "name": "@opencode-ai/http-recorder",
   "type": "module",
   "license": "MIT",
diff --git a/packages/llm/package.json b/packages/llm/package.json
index 4070681cf..a65f879a1 100644
--- a/packages/llm/package.json
+++ b/packages/llm/package.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "name": "@opencode-ai/llm",
   "type": "module",
   "license": "MIT",
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index f3cea500c..ba7b22c69 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "name": "opencode",
   "type": "module",
   "license": "MIT",
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index 82fdc0ab8..a661c0f6b 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.14.48",
+  "version": "1.14.49",
   "type": "module",
   "license": "MIT",
   "scripts": {
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index 65fbf98f0..d7c974ed6 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.14.48",
+  "version": "1.14.49",
   "type": "module",
   "license": "MIT",
   "scripts": {
diff --git a/packages/slack/package.json b/packages/slack/package.json
index 55e09f5d3..5d84fed54 100644
--- a/packages/slack/package.json
+++ b/packages/slack/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/slack",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "type": "module",
   "license": "MIT",
   "scripts": {
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 12441c8d0..f4aca0d68 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/ui",
-  "version": "1.14.48",
+  "version": "1.14.49",
   "type": "module",
   "license": "MIT",
   "exports": {
diff --git a/packages/web/package.json b/packages/web/package.json
index ec542077b..36df1907e 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.14.48",
+  "version": "1.14.49",
   "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 01737ee4e..0eeafe257 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.14.48",
+  "version": "1.14.49",
   "publisher": "sst-dev",
   "repository": {
     "type": "git",

From 5182a3698d3e0adcf489a6e57e71e315682d190c Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 18:22:51 -0400
Subject: [PATCH 299/378] test(workspace): use Effect for local session warp
 cases (#27393)

---
 .../test/control-plane/workspace.test.ts      | 230 ++++++++++--------
 1 file changed, 133 insertions(+), 97 deletions(-)

diff --git a/packages/opencode/test/control-plane/workspace.test.ts b/packages/opencode/test/control-plane/workspace.test.ts
index 01304e805..6533e6b7b 100644
--- a/packages/opencode/test/control-plane/workspace.test.ts
+++ b/packages/opencode/test/control-plane/workspace.test.ts
@@ -749,30 +749,39 @@ describe("workspace CRUD", () => {
     })
   })
 
-  test("remove deletes the workspace, associated sessions, adapter resources, and status", async () => {
-    await withInstance(async (dir) => {
-      const type = unique("remove-local")
-      const recorded = localAdapter(path.join(dir, "remove-local"))
-      registerAdapter(Instance.project.id, type, recorded.adapter)
-      const info = await createWorkspace({ type, branch: null, projectID: Instance.project.id, extra: null })
-      const one = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
-      const two = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
-      attachSessionToWorkspace(one.id, info.id)
-      attachSessionToWorkspace(two.id, info.id)
-
-      const removed = await removeWorkspace(info.id)
-
-      expect(removed).toEqual(info)
-      expect(await getWorkspace(info.id)).toBeUndefined()
-      expect(recorded.calls.remove).toEqual([info])
-      expect((await workspaceStatus()).find((item) => item.workspaceID === info.id)?.status).toBeUndefined()
-      expect(
-        Database.use((db) =>
-          db.select({ id: SessionTable.id }).from(SessionTable).where(eq(SessionTable.workspace_id, info.id)).all(),
-        ),
-      ).toEqual([])
-    })
-  })
+  it.instance(
+    "remove deletes the workspace, associated sessions, adapter resources, and status",
+    () => {
+      return Effect.gen(function* () {
+        const { directory: dir } = yield* TestInstance
+        const instance = yield* InstanceRef
+        if (!instance) return yield* Effect.die(new Error("missing test instance"))
+        const workspace = yield* Workspace.Service
+        const sessionSvc = yield* SessionNs.Service
+        const type = unique("remove-local")
+        const recorded = localAdapter(path.join(dir, "remove-local"))
+        registerAdapter(instance.project.id, type, recorded.adapter)
+        const info = yield* workspace.create({ type, branch: null, projectID: instance.project.id, extra: null })
+        const one = yield* sessionSvc.create({})
+        const two = yield* sessionSvc.create({})
+        attachSessionToWorkspace(one.id, info.id)
+        attachSessionToWorkspace(two.id, info.id)
+
+        const removed = yield* workspace.remove(info.id)
+
+        expect(removed).toEqual(info)
+        expect(yield* workspace.get(info.id)).toBeUndefined()
+        expect(recorded.calls.remove).toEqual([info])
+        expect((yield* workspace.status()).find((item) => item.workspaceID === info.id)?.status).toBeUndefined()
+        expect(
+          Database.use((db) =>
+            db.select({ id: SessionTable.id }).from(SessionTable).where(eq(SessionTable.workspace_id, info.id)).all(),
+          ),
+        ).toEqual([])
+      })
+    },
+    { git: true },
+  )
 
   test("remove still deletes the row when the adapter cannot remove resources", async () => {
     await withInstance(async () => {
@@ -797,84 +806,111 @@ describe("workspace CRUD", () => {
     })
   })
 
-  test("sessionWarp moves a session into a local workspace and claims ownership", async () => {
-    await withInstance(async (dir) => {
-      const previousType = unique("warp-prev-local")
-      const targetType = unique("warp-target-local")
-      const previous = workspaceInfo(Instance.project.id, previousType)
-      const target = workspaceInfo(Instance.project.id, targetType)
-      insertWorkspace(previous)
-      insertWorkspace(target)
-      registerAdapter(Instance.project.id, previousType, localAdapter(path.join(dir, "warp-prev-local")).adapter)
-      registerAdapter(Instance.project.id, targetType, localAdapter(path.join(dir, "warp-target-local")).adapter)
-      const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
-      attachSessionToWorkspace(session.id, previous.id)
-
-      await warpWorkspaceSession({ workspaceID: target.id, sessionID: session.id })
-
-      expect(
-        Database.use((db) =>
-          db
-            .select({ workspaceID: SessionTable.workspace_id })
-            .from(SessionTable)
-            .where(eq(SessionTable.id, session.id))
-            .get(),
-        )?.workspaceID,
-      ).toBe(target.id)
-      expect(sessionSequenceOwner(session.id)).toBe(target.id)
-    })
-  })
-
-  test("sessionWarp applies source workspace patch to local target workspace", async () => {
-    await withInstance(async (dir) => {
-      const previousType = unique("warp-patch-prev-local")
-      const targetType = unique("warp-patch-target-local")
-      const previousDir = path.join(dir, "warp-patch-prev-local")
-      const targetDir = path.join(dir, "warp-patch-target-local")
-      await initGitRepo(previousDir)
-      await initGitRepo(targetDir)
-      await fs.writeFile(path.join(previousDir, "tracked.txt"), "changed\n")
-      await fs.writeFile(path.join(previousDir, "new.txt"), "new\n")
-
-      const previous = workspaceInfo(Instance.project.id, previousType)
-      const target = workspaceInfo(Instance.project.id, targetType)
-      insertWorkspace(previous)
-      insertWorkspace(target)
-      registerAdapter(Instance.project.id, previousType, localAdapter(previousDir, { createDir: false }).adapter)
-      registerAdapter(Instance.project.id, targetType, localAdapter(targetDir, { createDir: false }).adapter)
-      const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
-      attachSessionToWorkspace(session.id, previous.id)
+  it.instance(
+    "sessionWarp moves a session into a local workspace and claims ownership",
+    () => {
+      return Effect.gen(function* () {
+        const { directory: dir } = yield* TestInstance
+        const instance = yield* InstanceRef
+        if (!instance) return yield* Effect.die(new Error("missing test instance"))
+        const workspace = yield* Workspace.Service
+        const sessionSvc = yield* SessionNs.Service
+        const previousType = unique("warp-prev-local")
+        const targetType = unique("warp-target-local")
+        const previous = workspaceInfo(instance.project.id, previousType)
+        const target = workspaceInfo(instance.project.id, targetType)
+        insertWorkspace(previous)
+        insertWorkspace(target)
+        registerAdapter(instance.project.id, previousType, localAdapter(path.join(dir, "warp-prev-local")).adapter)
+        registerAdapter(instance.project.id, targetType, localAdapter(path.join(dir, "warp-target-local")).adapter)
+        const session = yield* sessionSvc.create({})
+        attachSessionToWorkspace(session.id, previous.id)
+
+        yield* workspace.sessionWarp({ workspaceID: target.id, sessionID: session.id })
 
-      await warpWorkspaceSession({ workspaceID: target.id, sessionID: session.id, copyChanges: true })
+        expect(
+          Database.use((db) =>
+            db
+              .select({ workspaceID: SessionTable.workspace_id })
+              .from(SessionTable)
+              .where(eq(SessionTable.id, session.id))
+              .get(),
+          )?.workspaceID,
+        ).toBe(target.id)
+        expect(sessionSequenceOwner(session.id)).toBe(target.id)
+      })
+    },
+    { git: true },
+  )
 
-      expect(await fs.readFile(path.join(targetDir, "tracked.txt"), "utf8")).toBe("changed\n")
-      expect(await fs.readFile(path.join(targetDir, "new.txt"), "utf8")).toBe("new\n")
-    })
-  })
+  it.instance(
+    "sessionWarp applies source workspace patch to local target workspace",
+    () => {
+      return Effect.gen(function* () {
+        const { directory: dir } = yield* TestInstance
+        const instance = yield* InstanceRef
+        if (!instance) return yield* Effect.die(new Error("missing test instance"))
+        const workspace = yield* Workspace.Service
+        const sessionSvc = yield* SessionNs.Service
+        const previousType = unique("warp-patch-prev-local")
+        const targetType = unique("warp-patch-target-local")
+        const previousDir = path.join(dir, "warp-patch-prev-local")
+        const targetDir = path.join(dir, "warp-patch-target-local")
+        yield* Effect.promise(() => initGitRepo(previousDir))
+        yield* Effect.promise(() => initGitRepo(targetDir))
+        yield* Effect.promise(() => fs.writeFile(path.join(previousDir, "tracked.txt"), "changed\n"))
+        yield* Effect.promise(() => fs.writeFile(path.join(previousDir, "new.txt"), "new\n"))
+
+        const previous = workspaceInfo(instance.project.id, previousType)
+        const target = workspaceInfo(instance.project.id, targetType)
+        insertWorkspace(previous)
+        insertWorkspace(target)
+        registerAdapter(instance.project.id, previousType, localAdapter(previousDir, { createDir: false }).adapter)
+        registerAdapter(instance.project.id, targetType, localAdapter(targetDir, { createDir: false }).adapter)
+        const session = yield* sessionSvc.create({})
+        attachSessionToWorkspace(session.id, previous.id)
+
+        yield* workspace.sessionWarp({ workspaceID: target.id, sessionID: session.id, copyChanges: true })
+
+        expect(yield* Effect.promise(() => fs.readFile(path.join(targetDir, "tracked.txt"), "utf8"))).toBe("changed\n")
+        expect(yield* Effect.promise(() => fs.readFile(path.join(targetDir, "new.txt"), "utf8"))).toBe("new\n")
+      })
+    },
+    { git: true },
+  )
 
-  test("sessionWarp detaches a session to the local project and claims project ownership", async () => {
-    await withInstance(async (dir) => {
-      const previousType = unique("warp-detach-local")
-      const previous = workspaceInfo(Instance.project.id, previousType)
-      insertWorkspace(previous)
-      registerAdapter(Instance.project.id, previousType, localAdapter(path.join(dir, "warp-detach-local")).adapter)
-      const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
-      attachSessionToWorkspace(session.id, previous.id)
+  it.instance(
+    "sessionWarp detaches a session to the local project and claims project ownership",
+    () => {
+      return Effect.gen(function* () {
+        const { directory: dir } = yield* TestInstance
+        const instance = yield* InstanceRef
+        if (!instance) return yield* Effect.die(new Error("missing test instance"))
+        const workspace = yield* Workspace.Service
+        const sessionSvc = yield* SessionNs.Service
+        const previousType = unique("warp-detach-local")
+        const previous = workspaceInfo(instance.project.id, previousType)
+        insertWorkspace(previous)
+        registerAdapter(instance.project.id, previousType, localAdapter(path.join(dir, "warp-detach-local")).adapter)
+        const session = yield* sessionSvc.create({})
+        attachSessionToWorkspace(session.id, previous.id)
 
-      await warpWorkspaceSession({ workspaceID: null, sessionID: session.id })
+        yield* workspace.sessionWarp({ workspaceID: null, sessionID: session.id })
 
-      expect(
-        Database.use((db) =>
-          db
-            .select({ workspaceID: SessionTable.workspace_id })
-            .from(SessionTable)
-            .where(eq(SessionTable.id, session.id))
-            .get(),
-        )?.workspaceID,
-      ).toBeNull()
-      expect(sessionSequenceOwner(session.id)).toBe(Instance.project.id)
-    })
-  })
+        expect(
+          Database.use((db) =>
+            db
+              .select({ workspaceID: SessionTable.workspace_id })
+              .from(SessionTable)
+              .where(eq(SessionTable.id, session.id))
+              .get(),
+          )?.workspaceID,
+        ).toBeNull()
+        expect(sessionSequenceOwner(session.id)).toBe(instance.project.id)
+      })
+    },
+    { git: true },
+  )
 
   test("sessionWarp detaches to the source project when invoked from a workspace instance", async () => {
     await withInstance(async () => {

From 55e0af1405ea42a455008d896a23232f8541270a Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 18:23:09 -0400
Subject: [PATCH 300/378] fix(provider): type model not found errors (#27334)

---
 .../specs/effect/error-boundaries-plan.md     | 235 ++++++++++++++++++
 packages/opencode/specs/effect/errors.md      |   3 +
 packages/opencode/specs/effect/todo.md        |   5 +-
 packages/opencode/src/agent/agent.ts          |  13 +-
 packages/opencode/src/cli/error.ts            |  15 +-
 packages/opencode/src/effect/promise.ts       |  17 ++
 packages/opencode/src/provider/provider.ts    |  65 ++---
 .../instance/httpapi/middleware/error.ts      |  11 +-
 packages/opencode/src/session/compaction.ts   |   8 +-
 packages/opencode/src/session/prompt.ts       |  10 +-
 packages/opencode/test/cli/error.test.ts      |  17 ++
 .../opencode/test/provider/provider.test.ts   |  10 +-
 12 files changed, 340 insertions(+), 69 deletions(-)
 create mode 100644 packages/opencode/specs/effect/error-boundaries-plan.md
 create mode 100644 packages/opencode/src/effect/promise.ts

diff --git a/packages/opencode/specs/effect/error-boundaries-plan.md b/packages/opencode/specs/effect/error-boundaries-plan.md
new file mode 100644
index 000000000..763bf5ea5
--- /dev/null
+++ b/packages/opencode/specs/effect/error-boundaries-plan.md
@@ -0,0 +1,235 @@
+# Error Boundaries Plan
+
+Plan for removing `NamedError` as connective tissue while keeping public
+wire contracts stable.
+
+## Desired Shape
+
+```text
+Domain/service error
+  Schema.TaggedErrorClass
+  - catchable with catchTag / catchTags
+  - appears in service method error type
+  - no HTTP status
+  - no toObject()
+
+HTTP public error
+  Schema.ErrorClass / TaggedErrorClass with httpApiStatus
+  - endpoint-declared public contract
+  - owns legacy { name, data } only when that is the SDK wire shape
+
+CLI/user rendering
+  FormatError and small format helpers
+  - converts domain errors to text
+  - preserves useful structured fields
+
+Session/model-visible error
+  first-class session/message error schema or helper
+  - owns { name, data } event/message shape
+  - not a service error class
+```
+
+The important rule: a service error should not also be the HTTP body, CLI
+formatter, and session event body. Each seam adapts the error into the
+shape it owns.
+
+## Concrete Example: Provider Model Not Found
+
+Before:
+
+```ts
+export const ModelNotFoundError = NamedError.create("ProviderModelNotFoundError", {
+  providerID: ProviderID,
+  modelID: ModelID,
+  suggestions: Schema.optional(Schema.Array(Schema.String)),
+})
+```
+
+Problems:
+
+- Throwing it inside `Effect.fn` made it behave like a defect unless a
+  compatibility bridge caught it.
+- HTTP middleware knew that this one domain error should be a `400`.
+- Callers read `.data.*`, which couples them to the legacy `{ name, data }`
+  wire shape.
+
+After:
+
+```ts
+export class ModelNotFoundError extends Schema.TaggedErrorClass()("ProviderModelNotFoundError", {
+  providerID: ProviderID,
+  modelID: ModelID,
+  suggestions: Schema.optional(Schema.Array(Schema.String)),
+  cause: Schema.optional(Schema.Defect),
+}) {}
+
+export interface Interface {
+  readonly getModel: (providerID: ProviderID, modelID: ModelID) => Effect.Effect
+}
+```
+
+Boundary adapters:
+
+```text
+CLI
+└─ FormatError sees _tag ProviderModelNotFoundError -> nice text
+
+Session prompt
+└─ catch ModelNotFoundError -> publish Session.Event.Error as message/session wire shape
+
+HTTP route
+└─ catch ModelNotFoundError -> declared BadRequest public API error when the endpoint needs it
+
+HTTP middleware
+└─ no Provider.ModelNotFoundError knowledge
+```
+
+## Refining Known Promise Failures
+
+Use `EffectPromise.refineRejection(...)` when a Promise boundary can reject
+with many unknown values, but only one or two rejection classes are expected
+domain failures. Unknown rejections stay defects; the helper maps only known
+rejection shapes to typed errors.
+
+```ts
+const language =
+  yield *
+  EffectPromise.refineRejection(
+    async () => loadFromProvider(),
+    (cause) => (cause instanceof NoSuchModelError ? new ModelNotFoundError({ providerID, modelID, cause }) : undefined),
+  )
+```
+
+Use this when the Promise can genuinely reject and most rejection values are
+still defects for the current module. Use `Effect.tryPromise({ try, catch })`
+when every rejection should become the same expected error type. Use
+`Effect.promise(...)` only when rejection means a defect and you do not need
+to refine known rejection classes.
+
+## Helper Modules We Probably Want
+
+Add helpers only when repeated call sites prove the seam is real.
+
+### HTTP API Errors
+
+Likely location: `src/server/routes/instance/httpapi/errors.ts`.
+
+Purpose:
+
+- construct public HTTP error bodies
+- preserve legacy `{ name, data }` where needed
+- attach `httpApiStatus`
+
+Good helpers:
+
+```ts
+notFound(message)
+badRequest(message)
+unknown()
+```
+
+Avoid:
+
+```ts
+mapAnyDomainError(error)
+```
+
+That recreates the giant middleware mapper problem.
+
+### Session / Message Error Wire Helpers
+
+Likely location: near `src/session/message-error.ts` or a new narrow
+module such as `src/session/event-error.ts`.
+
+Purpose:
+
+- construct the `{ name, data }` shape used by `Session.Event.Error` and
+  assistant message errors
+- replace `new NamedError.Unknown(...).toObject()` call sites
+- keep model-visible error bodies separate from service/domain errors
+
+Good helpers:
+
+```ts
+unknown(message)
+agentNotFound(agent, available)
+commandNotFound(command, available)
+modelNotFound(error: Provider.ModelNotFoundError)
+```
+
+### CLI Formatters
+
+Likely location: `src/cli/error.ts` until repetition demands domain-local
+format helpers.
+
+Purpose:
+
+- produce human-readable terminal messages from typed errors
+- support old `{ name, data }` shapes only while compatibility is needed
+
+## Migration Queue
+
+### Remove Domain Knowledge From HTTP Middleware
+
+- [x] Storage not found no longer maps through defect fallback.
+- [x] Worktree expected errors moved to typed errors.
+- [x] Provider auth expected errors moved to typed errors.
+- [x] Provider model not found no longer needs an HTTP middleware status
+      special case.
+- [ ] Convert `Session.BusyError` and map it at route boundaries.
+- [ ] Delete the broad `NamedError` middleware branch once no route relies
+      on defect-wrapped legacy domain errors.
+- [ ] Keep one final unknown-defect fallback that logs `Cause.pretty(cause)`
+      and returns a safe `500` body.
+
+### Remaining `NamedError.create(...)` Service Errors
+
+These should become `Schema.TaggedErrorClass` when touched:
+
+- [ ] `src/provider/provider.ts` — `ProviderInitError`.
+- [ ] `src/storage/db.ts` — database `NotFoundError`.
+- [ ] `src/mcp/index.ts` — `MCPFailed`.
+- [ ] `src/skill/index.ts` — `SkillInvalidError`,
+      `SkillNameMismatchError`.
+- [ ] `src/lsp/client.ts` — `LSPInitializeError`.
+- [ ] `src/ide/index.ts` — install errors.
+- [ ] `src/config/error.ts`, `src/config/config.ts`,
+      `src/config/markdown.ts` — config errors. These already render well
+      in the CLI, so migrate carefully and preserve diagnostics.
+
+### Session / Message Wire Errors
+
+These are not ordinary service errors. They mostly build `{ name, data }`
+objects for model-visible/session-visible output.
+
+- [ ] Add a first-class session/message error wire helper.
+- [ ] Replace `new NamedError.Unknown(...).toObject()` in
+      `src/session/prompt.ts`.
+- [ ] Replace `new NamedError.Unknown(...).toObject()` in config/skill/plugin
+      session event publishing.
+- [ ] Move `src/session/message-error.ts` and `src/session/message-v2.ts`
+      away from `NamedError.create(...)` once the wire helper exists.
+- [ ] Update retry/message tests to assert the wire schema/helper output,
+      not `NamedError` instances.
+
+### CLI Rendering
+
+- [x] Tagged config errors render with useful diagnostics.
+- [x] Provider model not found renders from both old `{ name, data }` and
+      new `_tag` shapes.
+- [ ] Add typed render cases as more `NamedError.create(...)` domains move
+      to `Schema.TaggedErrorClass`.
+- [ ] Eventually remove old-shape compatibility branches when no callers can
+      produce them.
+
+## PR Checklist
+
+For each migrated error:
+
+- [ ] Domain error is `Schema.TaggedErrorClass`.
+- [ ] Service method exposes the typed error in its error channel.
+- [ ] No service error has `toObject()` just for compatibility.
+- [ ] CLI, HTTP, and session/message adapters each own their output shape.
+- [ ] HTTP middleware gets smaller or stays unchanged.
+- [ ] Focused tests cover the domain error and any public rendering/wire
+      shape touched by the PR.
diff --git a/packages/opencode/specs/effect/errors.md b/packages/opencode/specs/effect/errors.md
index 5266ca510..69298bde5 100644
--- a/packages/opencode/specs/effect/errors.md
+++ b/packages/opencode/specs/effect/errors.md
@@ -4,6 +4,9 @@ This note expands the `ERR`, `RENDER`, and `HTTP` tracks from
 [`todo.md`](./todo.md). It is the current reference for expected failures,
 typed service errors, and HTTP error boundaries.
 
+For the migration architecture and queue, see
+[`error-boundaries-plan.md`](./error-boundaries-plan.md).
+
 ## Goal
 
 - Expected service failures live on the Effect error channel.
diff --git a/packages/opencode/specs/effect/todo.md b/packages/opencode/specs/effect/todo.md
index 9261811c3..0c10f1227 100644
--- a/packages/opencode/specs/effect/todo.md
+++ b/packages/opencode/specs/effect/todo.md
@@ -2,8 +2,9 @@
 
 Short roadmap for Effect cleanup in `packages/opencode`.
 
-Current patterns and examples live in [`guide.md`](./guide.md). Test
-migration rules live in
+Current patterns and examples live in [`guide.md`](./guide.md). Error
+boundary migration details live in
+[`error-boundaries-plan.md`](./error-boundaries-plan.md). Test migration rules live in
 [`test/EFFECT_TEST_MIGRATION.md`](../../test/EFFECT_TEST_MIGRATION.md).
 Older deep-dive notes in this directory may still be useful, but treat
 this roadmap and the guide as the current entry points.
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index 1e4d7e156..ce6cf30b6 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -62,11 +62,14 @@ export interface Interface {
   readonly generate: (input: {
     description: string
     model?: { providerID: ProviderID; modelID: ModelID }
-  }) => Effect.Effect<{
-    identifier: string
-    whenToUse: string
-    systemPrompt: string
-  }>
+  }) => Effect.Effect<
+    {
+      identifier: string
+      whenToUse: string
+      systemPrompt: string
+    },
+    Provider.ModelNotFoundError
+  >
 }
 
 type State = Omit
diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts
index 2bb0cb51f..ffb0e0cfb 100644
--- a/packages/opencode/src/cli/error.ts
+++ b/packages/opencode/src/cli/error.ts
@@ -1,5 +1,6 @@
 import { NamedError } from "@opencode-ai/core/util/error"
 import { errorFormat } from "@/util/error"
+import { isRecord } from "@/util/record"
 
 interface ErrorLike {
   name?: string
@@ -10,10 +11,6 @@ interface ErrorLike {
 
 type ConfigIssue = { message: string; path: string[] }
 
-function isRecord(input: unknown): input is Record {
-  return typeof input === "object" && input !== null
-}
-
 function isTaggedError(error: unknown, tag: string): boolean {
   return isRecord(error) && error._tag === tag
 }
@@ -61,11 +58,13 @@ export function FormatError(input: unknown) {
   }
 
   // ProviderModelNotFoundError: { providerID: string, modelID: string, suggestions?: string[] }
-  if (NamedError.hasName(input, "ProviderModelNotFoundError")) {
-    const data = (input as ErrorLike).data
-    const suggestions = Array.isArray(data?.suggestions) ? data.suggestions.filter((x) => typeof x === "string") : []
+  const providerModelNotFound = configData(input, "ProviderModelNotFoundError")
+  if (providerModelNotFound) {
+    const suggestions = Array.isArray(providerModelNotFound.suggestions)
+      ? providerModelNotFound.suggestions.filter((x) => typeof x === "string")
+      : []
     return [
-      `Model not found: ${data?.providerID}/${data?.modelID}`,
+      `Model not found: ${providerModelNotFound.providerID}/${providerModelNotFound.modelID}`,
       ...(suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
       `Try: \`opencode models\` to list available models`,
       `Or check your config (opencode.json) provider/model names`,
diff --git a/packages/opencode/src/effect/promise.ts b/packages/opencode/src/effect/promise.ts
new file mode 100644
index 000000000..d7918796a
--- /dev/null
+++ b/packages/opencode/src/effect/promise.ts
@@ -0,0 +1,17 @@
+import { Cause, Effect } from "effect"
+
+export function refineRejection(
+  evaluate: (signal: AbortSignal) => PromiseLike,
+  refine: (cause: unknown) => E | undefined,
+) {
+  return Effect.tryPromise(evaluate).pipe(
+    Effect.catch((error) => {
+      const cause = Cause.isUnknownError(error) ? error.cause : error
+      const refined = refine(cause)
+      if (refined !== undefined) return Effect.fail(refined)
+      return Effect.die(cause)
+    }),
+  )
+}
+
+export * as EffectPromise from "./promise"
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 0f757caf1..46c1c56d6 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -21,6 +21,7 @@ import { pathToFileURL } from "url"
 import { Effect, Layer, Context, Schema, Types } from "effect"
 import { EffectBridge } from "@/effect/bridge"
 import { InstanceState } from "@/effect/instance-state"
+import { EffectPromise } from "@/effect/promise"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { isRecord } from "@/util/record"
 import { optionalOmitUndefined } from "@opencode-ai/core/schema"
@@ -963,11 +964,24 @@ export function defaultModelIDs sort(Object.values(item.models))[0].id)
 }
 
+export class ModelNotFoundError extends Schema.TaggedErrorClass()("ProviderModelNotFoundError", {
+  providerID: ProviderID,
+  modelID: ModelID,
+  suggestions: Schema.optional(Schema.Array(Schema.String)),
+  cause: Schema.optional(Schema.Defect),
+}) {
+  static isInstance(input: unknown): input is ModelNotFoundError {
+    return input instanceof ModelNotFoundError
+  }
+}
+
+export type Error = ModelNotFoundError
+
 export interface Interface {
   readonly list: () => Effect.Effect>
   readonly getProvider: (providerID: ProviderID) => Effect.Effect
-  readonly getModel: (providerID: ProviderID, modelID: ModelID) => Effect.Effect
-  readonly getLanguage: (model: Model) => Effect.Effect
+  readonly getModel: (providerID: ProviderID, modelID: ModelID) => Effect.Effect
+  readonly getLanguage: (model: Model) => Effect.Effect
   readonly closest: (
     providerID: ProviderID,
     query: string[],
@@ -1638,14 +1652,14 @@ const layer = Layer.effect(
           : fuzzysort
               .go(providerID, Object.keys({ ...s.catalog, ...s.providers }), { limit: 3, threshold: -10000 })
               .map((m) => m.target)
-        throw new ModelNotFoundError({ providerID, modelID, suggestions })
+        return yield* new ModelNotFoundError({ providerID, modelID, suggestions })
       }
 
       const info = provider.models[modelID]
       if (!info) {
         const current = modelSuggestions(provider, modelID)
         const suggestions = current.length ? current : modelSuggestions(s.catalog[providerID], modelID)
-        throw new ModelNotFoundError({ providerID, modelID, suggestions })
+        return yield* new ModelNotFoundError({ providerID, modelID, suggestions })
       }
       return info
     })
@@ -1656,11 +1670,10 @@ const layer = Layer.effect(
       const key = `${model.providerID}/${model.id}`
       if (s.models.has(key)) return s.models.get(key)!
 
-      return yield* Effect.promise(async () => {
-        const provider = s.providers[model.providerID]
-        const sdk = await resolveSDK(model, s, envs)
-
-        try {
+      const provider = s.providers[model.providerID]
+      return yield* EffectPromise.refineRejection(
+        async () => {
+          const sdk = await resolveSDK(model, s, envs)
           const language = s.modelLoaders[model.providerID]
             ? await s.modelLoaders[model.providerID](sdk, model.api.id, {
                 ...provider.options,
@@ -1669,18 +1682,12 @@ const layer = Layer.effect(
             : sdk.languageModel(model.api.id)
           s.models.set(key, language)
           return language
-        } catch (e) {
-          if (e instanceof NoSuchModelError)
-            throw new ModelNotFoundError(
-              {
-                modelID: model.id,
-                providerID: model.providerID,
-              },
-              { cause: e },
-            )
-          throw e
-        }
-      })
+        },
+        (cause) =>
+          cause instanceof NoSuchModelError
+            ? new ModelNotFoundError({ modelID: model.id, providerID: model.providerID, cause })
+            : undefined,
+      )
     })
 
     const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderID, query: string[]) {
@@ -1700,7 +1707,7 @@ const layer = Layer.effect(
 
       if (cfg.small_model) {
         const parsed = parseModel(cfg.small_model)
-        return yield* getModel(parsed.providerID, parsed.modelID)
+        return yield* getModel(parsed.providerID, parsed.modelID).pipe(Effect.orDie)
       }
 
       const s = yield* InstanceState.get(state)
@@ -1728,22 +1735,22 @@ const layer = Layer.effect(
           const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
 
           const globalMatch = candidates.find((m) => m.startsWith("global."))
-          if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch))
+          if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch)).pipe(Effect.orDie)
 
           const region = provider.options?.region
           if (region) {
             const regionPrefix = region.split("-")[0]
             if (regionPrefix === "us" || regionPrefix === "eu") {
               const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
-              if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch))
+              if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch)).pipe(Effect.orDie)
             }
           }
 
           const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
-          if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed))
+          if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed)).pipe(Effect.orDie)
         } else {
           for (const model of Object.keys(provider.models)) {
-            if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model))
+            if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model)).pipe(Effect.orDie)
           }
         }
       }
@@ -1818,12 +1825,6 @@ export function parseModel(model: string) {
   }
 }
 
-export const ModelNotFoundError = NamedError.create("ProviderModelNotFoundError", {
-  providerID: ProviderID,
-  modelID: ModelID,
-  suggestions: Schema.optional(Schema.Array(Schema.String)),
-})
-
 export const InitError = NamedError.create("ProviderInitError", {
   providerID: ProviderID,
 })
diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts
index 589641037..d4b6dbbab 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts
@@ -1,5 +1,3 @@
-import { Provider } from "@/provider/provider"
-import { iife } from "@/util/iife"
 import { NamedError } from "@opencode-ai/core/util/error"
 import * as Log from "@opencode-ai/core/util/log"
 import { Cause, Effect } from "effect"
@@ -23,14 +21,7 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect)
       log.error("failed", { error, cause: Cause.pretty(cause) })
 
       if (error instanceof NamedError) {
-        return Effect.succeed(
-          HttpServerResponse.jsonUnsafe(error.toObject(), {
-            status: iife(() => {
-              if (error instanceof Provider.ModelNotFoundError) return 400
-              return 500
-            }),
-          }),
-        )
+        return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 500 }))
       }
       return Effect.succeed(
         HttpServerResponse.jsonUnsafe(
diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts
index 3340e0fbe..fabdc01f0 100644
--- a/packages/opencode/src/session/compaction.ts
+++ b/packages/opencode/src/session/compaction.ts
@@ -390,8 +390,8 @@ export const layer: Layer.Layer<
 
       const agent = yield* agents.get("compaction")
       const model = agent.model
-        ? yield* provider.getModel(agent.model.providerID, agent.model.modelID)
-        : yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID)
+        ? yield* provider.getModel(agent.model.providerID, agent.model.modelID).pipe(Effect.orDie)
+        : yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID).pipe(Effect.orDie)
       const cfg = yield* config.get()
       const history = compactionPart && messages.at(-1)?.info.id === input.parentID ? messages.slice(0, -1) : messages
       const prior = completedCompactions(history)
@@ -519,7 +519,9 @@ export const layer: Layer.Layer<
               {
                 sessionID: input.sessionID,
                 agent: userMessage.agent,
-                model: yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID),
+                model: yield* provider
+                  .getModel(userMessage.model.providerID, userMessage.model.modelID)
+                  .pipe(Effect.orDie),
                 provider: {
                   source: info.source,
                   info,
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index bc58fbdf3..1ae411426 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -1057,15 +1057,15 @@ NOTE: At any point in time through this workflow you should feel free to ask the
       if (Exit.isSuccess(exit)) return exit.value
       const err = Cause.squash(exit.cause)
       if (Provider.ModelNotFoundError.isInstance(err)) {
-        const hint = err.data.suggestions?.length ? ` Did you mean: ${err.data.suggestions.join(", ")}?` : ""
+        const hint = err.suggestions?.length ? ` Did you mean: ${err.suggestions.join(", ")}?` : ""
         yield* bus.publish(Session.Event.Error, {
           sessionID,
           error: new NamedError.Unknown({
-            message: `Model not found: ${err.data.providerID}/${err.data.modelID}.${hint}`,
+            message: `Model not found: ${err.providerID}/${err.modelID}.${hint}`,
           }).toObject(),
         })
       }
-      return yield* Effect.failCause(exit.cause)
+      return yield* Effect.die(err)
     })
 
     const currentModel = Effect.fnUntraced(function* (sessionID: SessionID) {
@@ -1108,7 +1108,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the
       const same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID
       const full =
         !input.variant && ag.variant && same
-          ? yield* provider.getModel(model.providerID, model.modelID).pipe(Effect.catchDefect(() => Effect.void))
+          ? yield* provider
+              .getModel(model.providerID, model.modelID)
+              .pipe(Effect.catchIf(Provider.ModelNotFoundError.isInstance, () => Effect.succeed(undefined)))
           : undefined
       const variant = input.variant ?? (ag.variant && full?.variants?.[ag.variant] ? ag.variant : undefined)
 
diff --git a/packages/opencode/test/cli/error.test.ts b/packages/opencode/test/cli/error.test.ts
index 6c5ab265b..63e04695d 100644
--- a/packages/opencode/test/cli/error.test.ts
+++ b/packages/opencode/test/cli/error.test.ts
@@ -64,6 +64,23 @@ describe("cli.error", () => {
     expect(formatted).toContain("Check your network, proxy, or VPN configuration and try again.")
   })
 
+  test("formats legacy and tagged provider model errors the same way", () => {
+    const data = {
+      providerID: "anthropic",
+      modelID: "claude-sonet-4",
+      suggestions: ["claude-sonnet-4"],
+    }
+    const expected = [
+      "Model not found: anthropic/claude-sonet-4",
+      "Did you mean: claude-sonnet-4",
+      "Try: `opencode models` to list available models",
+      "Or check your config (opencode.json) provider/model names",
+    ].join("\n")
+
+    expect(FormatError({ name: "ProviderModelNotFoundError", data })).toBe(expected)
+    expect(FormatError({ _tag: "ProviderModelNotFoundError", ...data })).toBe(expected)
+  })
+
   test("formats cancelled UI errors as empty output", () => {
     expect(FormatError(new UI.CancelledError())).toBe("")
   })
diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts
index ab39c1902..aafc19255 100644
--- a/packages/opencode/test/provider/provider.test.ts
+++ b/packages/opencode/test/provider/provider.test.ts
@@ -1572,8 +1572,8 @@ test("ModelNotFoundError includes suggestions for typos", async () => {
         await getModel(ProviderID.anthropic, ModelID.make("claude-sonet-4")) // typo: sonet instead of sonnet
         expect(true).toBe(false) // Should not reach here
       } catch (e: any) {
-        expect(e.data.suggestions).toBeDefined()
-        expect(e.data.suggestions.length).toBeGreaterThan(0)
+        expect(e.suggestions).toBeDefined()
+        expect(e.suggestions.length).toBeGreaterThan(0)
       }
     },
   })
@@ -1598,8 +1598,8 @@ test("ModelNotFoundError for provider includes suggestions", async () => {
         await getModel(ProviderID.make("antropic"), ModelID.make("claude-sonnet-4")) // typo: antropic
         expect(true).toBe(false) // Should not reach here
       } catch (e: any) {
-        expect(e.data.suggestions).toBeDefined()
-        expect(e.data.suggestions).toContain("anthropic")
+        expect(e.suggestions).toBeDefined()
+        expect(e.suggestions).toContain("anthropic")
       }
     },
   })
@@ -1625,7 +1625,7 @@ test("ModelNotFoundError suggests catalog models for unloaded providers", async
         throw new Error("expected model lookup to fail")
       } catch (e) {
         if (!Provider.ModelNotFoundError.isInstance(e)) throw e
-        expect(e.data.suggestions).toContain("claude-haiku-4-5")
+        expect(e.suggestions).toContain("claude-haiku-4-5")
       }
     },
   })

From df3895d74ff72973cc9320a59f760d841dd573b9 Mon Sep 17 00:00:00 2001
From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Date: Wed, 13 May 2026 18:30:36 -0500
Subject: [PATCH 301/378] cleanup: make smallOptions rely on variants (#27390)

---
 packages/opencode/src/provider/transform.ts   | 33 ++------
 .../opencode/test/provider/transform.test.ts  | 84 +++++++++++++++----
 2 files changed, 75 insertions(+), 42 deletions(-)

diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts
index 72ec881e7..1021d83f0 100644
--- a/packages/opencode/src/provider/transform.ts
+++ b/packages/opencode/src/provider/transform.ts
@@ -617,14 +617,6 @@ function googleThinkingBudgetMax(apiId: string) {
   return 24_576
 }
 
-function googleSmallThinkingConfig(apiId: string) {
-  const levels = googleThinkingLevelEfforts(apiId)
-  if (apiId.toLowerCase().includes("gemini-3")) {
-    return { thinkingLevel: levels.includes("minimal") ? "minimal" : levels.includes("low") ? "low" : "high" }
-  }
-  return { thinkingBudget: googleThinkingBudgetMax(apiId) === 32_768 ? 128 : 0 }
-}
-
 export function variants(model: Provider.Model): Record> {
   if (!model.capabilities.reasoning) return {}
 
@@ -1184,40 +1176,27 @@ export function options(input: {
 }
 
 export function smallOptions(model: Provider.Model) {
+  const small = Object.values(model.variants ?? {})[0] ?? {}
   if (
     model.providerID === "openai" ||
     model.api.npm === "@ai-sdk/openai" ||
     model.api.npm === "@ai-sdk/github-copilot"
   ) {
-    if (model.api.id.includes("gpt-5")) {
-      if (model.api.id.includes("-chat")) {
-        if (gpt5Version(model.api.id) === undefined) return { store: false }
-        return { store: false, reasoningEffort: "medium" }
-      }
-      if (model.api.id.includes("search-api")) return { store: false }
-      if (model.api.id.includes("5.") || model.api.id.includes("5-mini")) {
-        return { store: false, reasoningEffort: "low" }
-      }
-      return { store: false, reasoningEffort: "minimal" }
-    }
-    return { store: false }
-  }
-  if (model.providerID === "google") {
-    // gemini-3 uses thinkingLevel, gemini-2.5 uses thinkingBudget
-    return { thinkingConfig: googleSmallThinkingConfig(model.api.id) }
+    const base = { store: false }
+    return mergeDeep(base, small)
   }
   if (model.providerID === "openrouter" || model.providerID === "llmgateway") {
-    if (model.api.id.includes("google")) {
+    if (Object.keys(small).length === 0 && model.api.id.includes("google")) {
       return { reasoning: { enabled: false } }
     }
-    return { reasoningEffort: "minimal" }
   }
 
   if (model.providerID === "venice") {
+    if (Object.keys(small).length > 0) return small
     return { veniceParameters: { disableThinking: true } }
   }
 
-  return {}
+  return small
 }
 
 // Maps model ID prefix to provider slug used in providerOptions.
diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts
index df21922b0..9b6615ee8 100644
--- a/packages/opencode/test/provider/transform.test.ts
+++ b/packages/opencode/test/provider/transform.test.ts
@@ -3602,8 +3602,8 @@ describe("ProviderTransform.variants", () => {
 })
 
 describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => {
-  const createModel = (apiId: string) =>
-    ({
+  const createModel = (apiId: string) => {
+    const model = {
       id: `openai/${apiId}`,
       providerID: "openai",
       api: {
@@ -3611,13 +3611,43 @@ describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => {
         url: "https://api.openai.com",
         npm: "@ai-sdk/openai",
       },
-    }) as any
+      capabilities: { reasoning: true },
+      limit: { output: 64_000 },
+      release_date: "2026-01-01",
+    } as any
+    model.variants = ProviderTransform.variants(model)
+    return model
+  }
 
   for (const testCase of [
     { id: "gpt-5-chat-latest", options: { store: false } },
-    { id: "gpt-5.1-chat-latest", options: { store: false, reasoningEffort: "medium" } },
-    { id: "gpt-5.2-chat-latest", options: { store: false, reasoningEffort: "medium" } },
-    { id: "gpt-5-search-api", options: { store: false } },
+    {
+      id: "gpt-5.1-chat-latest",
+      options: {
+        store: false,
+        reasoningEffort: "medium",
+        reasoningSummary: "auto",
+        include: ["reasoning.encrypted_content"],
+      },
+    },
+    {
+      id: "gpt-5.2-chat-latest",
+      options: {
+        store: false,
+        reasoningEffort: "medium",
+        reasoningSummary: "auto",
+        include: ["reasoning.encrypted_content"],
+      },
+    },
+    {
+      id: "gpt-5-search-api",
+      options: {
+        store: false,
+        reasoningEffort: "none",
+        reasoningSummary: "auto",
+        include: ["reasoning.encrypted_content"],
+      },
+    },
   ]) {
     test(`${testCase.id} returns only supported small options`, () => {
       expect(ProviderTransform.smallOptions(createModel(testCase.id))).toEqual(testCase.options)
@@ -3626,8 +3656,8 @@ describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => {
 })
 
 describe("ProviderTransform.smallOptions - google thinking controls", () => {
-  const createGoogleModel = (apiId: string) =>
-    ({
+  const createGoogleModel = (apiId: string) => {
+    const model = {
       id: `google/${apiId}`,
       providerID: "google",
       api: {
@@ -3635,20 +3665,44 @@ describe("ProviderTransform.smallOptions - google thinking controls", () => {
         url: "https://generativelanguage.googleapis.com",
         npm: "@ai-sdk/google",
       },
-    }) as any
+      capabilities: { reasoning: true },
+      limit: { output: 64_000 },
+    } as any
+    model.variants = ProviderTransform.variants(model)
+    return model
+  }
 
   for (const testCase of [
-    { id: "gemini-3-pro-preview", options: { thinkingConfig: { thinkingLevel: "low" } } },
-    { id: "gemini-3-flash-preview", options: { thinkingConfig: { thinkingLevel: "minimal" } } },
-    { id: "gemini-3.1-flash-image-preview", options: { thinkingConfig: { thinkingLevel: "minimal" } } },
-    { id: "gemini-3-pro-image-preview", options: { thinkingConfig: { thinkingLevel: "high" } } },
-    { id: "gemini-2.5-pro", options: { thinkingConfig: { thinkingBudget: 128 } } },
-    { id: "gemini-2.5-flash", options: { thinkingConfig: { thinkingBudget: 0 } } },
+    { id: "gemini-3-pro-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } } },
+    { id: "gemini-3-flash-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "minimal" } } },
+    {
+      id: "gemini-3.1-flash-image-preview",
+      options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "minimal" } },
+    },
+    { id: "gemini-3-pro-image-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } } },
+    { id: "gemini-2.5-pro", options: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } },
+    { id: "gemini-2.5-flash", options: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } },
   ]) {
     test(`${testCase.id} returns supported small thinking options`, () => {
       expect(ProviderTransform.smallOptions(createGoogleModel(testCase.id))).toEqual(testCase.options)
     })
   }
+
+  test("uses the first configured variant when available", () => {
+    expect(
+      ProviderTransform.smallOptions({
+        ...createGoogleModel("gemini-2.5-pro"),
+        variants: {
+          high: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
+          max: { thinkingConfig: { includeThoughts: true, thinkingBudget: 32768 } },
+        },
+      }),
+    ).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } })
+  })
+
+  test("does not synthesize thinking options when variants are empty", () => {
+    expect(ProviderTransform.smallOptions({ ...createGoogleModel("gemini-2.5-pro"), variants: {} })).toEqual({})
+  })
 })
 
 describe("ProviderTransform.providerOptions - ai-gateway-provider", () => {

From de1e0b5d6d58bf2ec7abab82b0b90a80731ee836 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 19:49:03 -0400
Subject: [PATCH 302/378] test(workspace): effectify sync state cases (#27400)

---
 .../test/control-plane/workspace.test.ts      | 137 ++++++++++--------
 1 file changed, 77 insertions(+), 60 deletions(-)

diff --git a/packages/opencode/test/control-plane/workspace.test.ts b/packages/opencode/test/control-plane/workspace.test.ts
index 6533e6b7b..21bf4be6b 100644
--- a/packages/opencode/test/control-plane/workspace.test.ts
+++ b/packages/opencode/test/control-plane/workspace.test.ts
@@ -1040,21 +1040,29 @@ describe("workspace CRUD", () => {
 })
 
 describe("workspace sync state", () => {
-  test("startWorkspaceSyncing is disabled by the experimental workspace flag", async () => {
-    await withInstance(async (dir) => {
-      const type = unique("flag-disabled")
-      const info = workspaceInfo(Instance.project.id, type)
-      const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
-      attachSessionToWorkspace(session.id, info.id)
-      insertWorkspace(info)
-      registerAdapter(Instance.project.id, type, localAdapter(path.join(dir, "flag-disabled")).adapter)
+  it.instance(
+    "startWorkspaceSyncing is disabled by the experimental workspace flag",
+    () =>
+      Effect.gen(function* () {
+        const { directory: dir } = yield* TestInstance
+        const instance = yield* InstanceRef
+        if (!instance) return yield* Effect.die(new Error("missing test instance"))
+        const workspace = yield* Workspace.Service
+        const sessionSvc = yield* SessionNs.Service
+        const type = unique("flag-disabled")
+        const info = workspaceInfo(instance.project.id, type)
+        const session = yield* sessionSvc.create({})
+        attachSessionToWorkspace(session.id, info.id)
+        insertWorkspace(info)
+        registerAdapter(instance.project.id, type, localAdapter(path.join(dir, "flag-disabled")).adapter)
 
-      await startWorkspaceSyncingWithFlag(Instance.project.id, false)
-      await delay(25)
+        yield* Effect.promise(() => startWorkspaceSyncingWithFlag(instance.project.id, false))
+        yield* Effect.sleep("25 millis")
 
-      expect((await workspaceStatus()).find((item) => item.workspaceID === info.id)?.status).toBeUndefined()
-    })
-  })
+        expect((yield* workspace.status()).find((item) => item.workspaceID === info.id)?.status).toBeUndefined()
+      }),
+    { git: true },
+  )
 
   it.instance(
     "startWorkspaceSyncing starts all workspaces",
@@ -1094,67 +1102,76 @@ describe("workspace sync state", () => {
     { git: true },
   )
 
-  test("local start reports error when the target directory is missing", async () => {
-    await withInstance(async (dir) => {
-      const type = unique("missing-local")
-      const info = workspaceInfo(Instance.project.id, type)
-      insertWorkspace(info)
-      registerAdapter(
-        Instance.project.id,
-        type,
-        localAdapter(path.join(dir, "missing-target"), { createDir: false }).adapter,
-      )
-      attachSessionToWorkspace(
-        (await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))).id,
-        info.id,
-      )
+  it.instance(
+    "local start reports error when the target directory is missing",
+    () =>
+      Effect.gen(function* () {
+        const { directory: dir } = yield* TestInstance
+        const instance = yield* InstanceRef
+        if (!instance) return yield* Effect.die(new Error("missing test instance"))
+        const workspace = yield* Workspace.Service
+        const sessionSvc = yield* SessionNs.Service
+        const type = unique("missing-local")
+        const info = workspaceInfo(instance.project.id, type)
+        insertWorkspace(info)
+        registerAdapter(
+          instance.project.id,
+          type,
+          localAdapter(path.join(dir, "missing-target"), { createDir: false }).adapter,
+        )
+        attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id)
 
-      startWorkspaceSyncing(Instance.project.id)
+        yield* workspace.startWorkspaceSyncing(instance.project.id)
 
-      await eventually(() =>
-        workspaceStatus().then((status) =>
-          expect(status.find((item) => item.workspaceID === info.id)?.status).toBe("error"),
-        ),
-      )
-      expect(await isWorkspaceSyncing(info.id)).toBe(false)
-      await removeWorkspace(info.id)
-    })
-  })
+        yield* eventuallyEffect(
+          Effect.gen(function* () {
+            const status = yield* workspace.status()
+            expect(status.find((item) => item.workspaceID === info.id)?.status).toBe("error")
+          }),
+        )
+        expect(yield* workspace.isSyncing(info.id)).toBe(false)
+        yield* workspace.remove(info.id)
+      }),
+    { git: true },
+  )
 
-  test("duplicate local status updates are suppressed", async () => {
-    await withInstance(async (dir) => {
-      const captured = captureGlobalEvents()
-      try {
+  it.instance(
+    "duplicate local status updates are suppressed",
+    () =>
+      Effect.gen(function* () {
+        const { directory: dir } = yield* TestInstance
+        const instance = yield* InstanceRef
+        if (!instance) return yield* Effect.die(new Error("missing test instance"))
+        const workspace = yield* Workspace.Service
+        const sessionSvc = yield* SessionNs.Service
+        const captured = captureGlobalEvents()
+        yield* Effect.addFinalizer(() => Effect.sync(() => captured.dispose()))
         const type = unique("dedupe-local")
-        const info = workspaceInfo(Instance.project.id, type)
+        const info = workspaceInfo(instance.project.id, type)
         const target = path.join(dir, "dedupe-local")
-        await fs.mkdir(target, { recursive: true })
+        yield* Effect.promise(() => fs.mkdir(target, { recursive: true }))
         insertWorkspace(info)
-        registerAdapter(Instance.project.id, type, localAdapter(target).adapter)
-        attachSessionToWorkspace(
-          (await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))).id,
-          info.id,
-        )
+        registerAdapter(instance.project.id, type, localAdapter(target).adapter)
+        attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id)
 
-        startWorkspaceSyncing(Instance.project.id)
-        startWorkspaceSyncing(Instance.project.id)
+        yield* workspace.startWorkspaceSyncing(instance.project.id)
+        yield* workspace.startWorkspaceSyncing(instance.project.id)
 
-        await eventually(() =>
-          workspaceStatus().then((status) =>
-            expect(status.find((item) => item.workspaceID === info.id)?.status).toBe("connected"),
-          ),
+        yield* eventuallyEffect(
+          Effect.gen(function* () {
+            const status = yield* workspace.status()
+            expect(status.find((item) => item.workspaceID === info.id)?.status).toBe("connected")
+          }),
         )
         expect(
           captured.events.filter(
             (event) => event.workspace === info.id && event.payload.type === Workspace.Event.Status.type,
           ),
         ).toHaveLength(1)
-        await removeWorkspace(info.id)
-      } finally {
-        captured.dispose()
-      }
-    })
-  })
+        yield* workspace.remove(info.id)
+      }),
+    { git: true },
+  )
 
   it.live("remote start emits disconnected, connecting, and connected then refuses duplicate listeners", () => {
     const calls: FetchCall[] = []

From ccb207f946e79685a1c16a2135688c0dfde3f84c Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 20:25:37 -0400
Subject: [PATCH 303/378] effect(util): migrate filesystem callers to
 AppFileSystem.Service (#27152)

---
 packages/opencode/src/cli/cmd/import.ts       |  9 ++--
 .../opencode/src/control-plane/workspace.ts   |  6 ++-
 .../test/control-plane/workspace.test.ts      |  2 +
 packages/opencode/test/file/index.test.ts     | 49 ++++++++++---------
 .../test/plugin/loader-shared.test.ts         | 20 ++++----
 .../test/plugin/workspace-adapter.test.ts     |  1 +
 packages/opencode/test/tool/shell.test.ts     |  2 +-
 .../opencode/test/tool/truncation.test.ts     | 20 +++++---
 8 files changed, 63 insertions(+), 46 deletions(-)

diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts
index 419e81379..2fcf286f4 100644
--- a/packages/opencode/src/cli/cmd/import.ts
+++ b/packages/opencode/src/cli/cmd/import.ts
@@ -7,7 +7,7 @@ import { SessionTable, MessageTable, PartTable } from "../../session/session.sql
 import { InstanceRef } from "@/effect/instance-ref"
 import { ShareNext } from "@/share/share-next"
 import { EOL } from "os"
-import { Filesystem } from "@/util/filesystem"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { Effect, Schema } from "effect"
 
 const decodeMessageInfo = Schema.decodeUnknownSync(MessageV2.Info)
@@ -95,6 +95,7 @@ export const ImportCommand = effectCmd({
 
 const runImport = Effect.fn("Cli.import.body")(function* (file: string, projectID: string) {
   const share = yield* ShareNext.Service
+  const fs = yield* AppFileSystem.Service
 
   let exportData: ExportData | undefined
 
@@ -149,9 +150,9 @@ const runImport = Effect.fn("Cli.import.body")(function* (file: string, projectI
 
     exportData = transformed
   } else {
-    exportData = yield* Effect.promise(() =>
-      Filesystem.readJson>(file).catch(() => undefined),
-    )
+    exportData = (yield* fs.readJson(file).pipe(Effect.orElseSucceed(() => undefined))) as
+      | NonNullable
+      | undefined
     if (!exportData) {
       process.stdout.write(`File not found: ${file}`)
       process.stdout.write(EOL)
diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts
index 4a21e2e65..5b7f867ca 100644
--- a/packages/opencode/src/control-plane/workspace.ts
+++ b/packages/opencode/src/control-plane/workspace.ts
@@ -10,9 +10,9 @@ import { GlobalBus } from "@/bus/global"
 import { Auth } from "@/auth"
 import { SyncEvent } from "@/sync"
 import { EventSequenceTable, EventTable } from "@/sync/event.sql"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import * as Log from "@opencode-ai/core/util/log"
 import { RuntimeFlags } from "@/effect/runtime-flags"
-import { Filesystem } from "@/util/filesystem"
 import { ProjectID } from "@/project/schema"
 import { Slug } from "@opencode-ai/core/util/slug"
 import { WorkspaceTable } from "./workspace.sql"
@@ -176,6 +176,7 @@ export const layer = Layer.effect(
     const sync = yield* SyncEvent.Service
     const vcs = yield* Vcs.Service
     const flags = yield* RuntimeFlags.Service
+    const fs = yield* AppFileSystem.Service
     const connections = new Map()
     const syncFibers = yield* FiberMap.make()
 
@@ -501,7 +502,7 @@ export const layer = Layer.effect(
       if (!target) return
 
       if (target.type === "local") {
-        setStatus(space.id, (yield* Effect.promise(() => Filesystem.exists(target.directory))) ? "connected" : "error")
+        setStatus(space.id, (yield* fs.existsSafe(target.directory)) ? "connected" : "error")
         return
       }
 
@@ -1040,6 +1041,7 @@ export const defaultLayer = layer.pipe(
   Layer.provide(SessionPrompt.defaultLayer),
   Layer.provide(Project.defaultLayer),
   Layer.provide(Vcs.defaultLayer),
+  Layer.provide(AppFileSystem.defaultLayer),
   Layer.provide(FetchHttpClient.layer),
   Layer.provide(RuntimeFlags.defaultLayer),
 )
diff --git a/packages/opencode/test/control-plane/workspace.test.ts b/packages/opencode/test/control-plane/workspace.test.ts
index 21bf4be6b..1c8156ae6 100644
--- a/packages/opencode/test/control-plane/workspace.test.ts
+++ b/packages/opencode/test/control-plane/workspace.test.ts
@@ -8,6 +8,7 @@ import { NodeHttpServer } from "@effect/platform-node"
 import { Effect, Layer, Schema } from "effect"
 import { FetchHttpClient, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
 import { eq } from "drizzle-orm"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import * as Log from "@opencode-ai/core/util/log"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { GlobalBus, type GlobalEvent } from "@/bus/global"
@@ -59,6 +60,7 @@ const workspaceLayer = (experimentalWorkspaces: boolean) =>
     Layer.provide(Project.defaultLayer),
     Layer.provide(Vcs.defaultLayer),
     Layer.provide(FetchHttpClient.layer),
+    Layer.provide(AppFileSystem.defaultLayer),
     Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces })),
     Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))),
   )
diff --git a/packages/opencode/test/file/index.test.ts b/packages/opencode/test/file/index.test.ts
index 8b48fff5e..b7d531c63 100644
--- a/packages/opencode/test/file/index.test.ts
+++ b/packages/opencode/test/file/index.test.ts
@@ -5,7 +5,6 @@ import { Cause, Effect, Exit, Layer } from "effect"
 import path from "path"
 import fs from "fs/promises"
 import { File } from "../../src/file"
-import { Filesystem } from "@/util/filesystem"
 import { disposeAllInstances, TestInstance, withTmpdirInstance } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
 
@@ -161,7 +160,7 @@ describe("file/index Filesystem patterns", () => {
         const filepath = path.join(test.directory, "test.json")
         yield* Effect.promise(() => fs.writeFile(filepath, '{"key": "value"}', "utf-8"))
 
-        expect(yield* Effect.promise(() => Filesystem.mimeType(filepath))).toContain("application/json")
+        expect(AppFileSystem.mimeType(filepath)).toContain("application/json")
 
         const result = yield* read("test.json")
         expect(result.type).toBe("text")
@@ -181,7 +180,7 @@ describe("file/index Filesystem patterns", () => {
         for (const testCase of testCases) {
           const filepath = path.join(test.directory, `test.${testCase.ext}`)
           yield* Effect.promise(() => fs.writeFile(filepath, Buffer.from([0x00, 0x00, 0x00, 0x00])))
-          expect(yield* Effect.promise(() => Filesystem.mimeType(filepath))).toContain(testCase.mime)
+          expect(AppFileSystem.mimeType(filepath)).toContain(testCase.mime)
         }
       }),
     )
@@ -189,15 +188,16 @@ describe("file/index Filesystem patterns", () => {
 
   describe("list() - Filesystem.exists() and readText()", () => {
     it.instance(
-      "reads .gitignore via Filesystem.exists() and readText()",
+      "reads .gitignore via AppFileSystem.existsSafe() and readFileString()",
       () =>
         Effect.gen(function* () {
+          const fsys = yield* AppFileSystem.Service
           const test = yield* TestInstance
           const gitignorePath = path.join(test.directory, ".gitignore")
-          yield* Effect.promise(() => fs.writeFile(gitignorePath, "node_modules\ndist\n", "utf-8"))
+          yield* fsys.writeFileString(gitignorePath, "node_modules\ndist\n")
 
-          expect(yield* Effect.promise(() => Filesystem.exists(gitignorePath))).toBe(true)
-          expect(yield* Effect.promise(() => Filesystem.readText(gitignorePath))).toContain("node_modules")
+          expect(yield* fsys.existsSafe(gitignorePath)).toBe(true)
+          expect(yield* fsys.readFileString(gitignorePath)).toContain("node_modules")
         }),
       { git: true },
     )
@@ -206,12 +206,13 @@ describe("file/index Filesystem patterns", () => {
       "reads .ignore file similarly",
       () =>
         Effect.gen(function* () {
+          const fsys = yield* AppFileSystem.Service
           const test = yield* TestInstance
           const ignorePath = path.join(test.directory, ".ignore")
-          yield* Effect.promise(() => fs.writeFile(ignorePath, "*.log\n.env\n", "utf-8"))
+          yield* fsys.writeFileString(ignorePath, "*.log\n.env\n")
 
-          expect(yield* Effect.promise(() => Filesystem.exists(ignorePath))).toBe(true)
-          expect(yield* Effect.promise(() => Filesystem.readText(ignorePath))).toContain("*.log")
+          expect(yield* fsys.existsSafe(ignorePath)).toBe(true)
+          expect(yield* fsys.readFileString(ignorePath)).toContain("*.log")
         }),
       { git: true },
     )
@@ -220,9 +221,10 @@ describe("file/index Filesystem patterns", () => {
       "handles missing .gitignore gracefully",
       () =>
         Effect.gen(function* () {
+          const fsys = yield* AppFileSystem.Service
           const test = yield* TestInstance
           const gitignorePath = path.join(test.directory, ".gitignore")
-          expect(yield* Effect.promise(() => Filesystem.exists(gitignorePath))).toBe(false)
+          expect(yield* fsys.existsSafe(gitignorePath)).toBe(false)
 
           const nodes = yield* list()
           expect(Array.isArray(nodes)).toBe(true)
@@ -231,16 +233,17 @@ describe("file/index Filesystem patterns", () => {
     )
   })
 
-  describe("File.changed() - Filesystem.readText() for untracked files", () => {
+  describe("File.changed() - AppFileSystem.readFileString() for untracked files", () => {
     it.instance(
-      "reads untracked files via Filesystem.readText()",
+      "reads untracked files via AppFileSystem.readFileString()",
       () =>
         Effect.gen(function* () {
+          const fsys = yield* AppFileSystem.Service
           const test = yield* TestInstance
           const untrackedPath = path.join(test.directory, "untracked.txt")
-          yield* Effect.promise(() => fs.writeFile(untrackedPath, "new content\nwith multiple lines", "utf-8"))
+          yield* fsys.writeFileString(untrackedPath, "new content\nwith multiple lines")
 
-          const content = yield* Effect.promise(() => Filesystem.readText(untrackedPath))
+          const content = yield* fsys.readFileString(untrackedPath)
           expect(content.split("\n").length).toBe(2)
         }),
       { git: true },
@@ -248,28 +251,26 @@ describe("file/index Filesystem patterns", () => {
   })
 
   describe("Error handling", () => {
-    it.instance("handles errors gracefully in Filesystem.readText()", () =>
+    it.instance("handles errors gracefully in AppFileSystem.readFileString()", () =>
       Effect.gen(function* () {
+        const fsys = yield* AppFileSystem.Service
         const test = yield* TestInstance
-        yield* Effect.promise(() => fs.writeFile(path.join(test.directory, "readonly.txt"), "content", "utf-8"))
+        yield* fsys.writeFileString(path.join(test.directory, "readonly.txt"), "content")
 
         const nonExistentPath = path.join(test.directory, "does-not-exist.txt")
-        expect(
-          Exit.isFailure(yield* Effect.promise(() => Filesystem.readText(nonExistentPath)).pipe(Effect.exit)),
-        ).toBe(true)
+        expect(Exit.isFailure(yield* fsys.readFileString(nonExistentPath).pipe(Effect.exit))).toBe(true)
 
         const result = yield* read("does-not-exist.txt")
         expect(result.content).toBe("")
       }),
     )
 
-    it.instance("handles errors in Filesystem.readArrayBuffer()", () =>
+    it.instance("handles errors in AppFileSystem.readFile()", () =>
       Effect.gen(function* () {
+        const fsys = yield* AppFileSystem.Service
         const test = yield* TestInstance
         const nonExistentPath = path.join(test.directory, "does-not-exist.bin")
-        const buffer = yield* Effect.promise(() =>
-          Filesystem.readArrayBuffer(nonExistentPath).catch(() => new ArrayBuffer(0)),
-        )
+        const buffer = yield* fsys.readFile(nonExistentPath).pipe(Effect.orElseSucceed(() => new Uint8Array(0)))
         expect(buffer.byteLength).toBe(0)
       }),
     )
diff --git a/packages/opencode/test/plugin/loader-shared.test.ts b/packages/opencode/test/plugin/loader-shared.test.ts
index 6b1dd306d..c28348863 100644
--- a/packages/opencode/test/plugin/loader-shared.test.ts
+++ b/packages/opencode/test/plugin/loader-shared.test.ts
@@ -4,9 +4,9 @@ import fs from "fs/promises"
 import path from "path"
 import { pathToFileURL } from "url"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
-import { Filesystem } from "@/util/filesystem"
 
 const { Plugin } = await import("../../src/plugin/index")
 const { PluginLoader } = await import("../../src/plugin/loader")
@@ -20,7 +20,7 @@ afterEach(async () => {
   await disposeAllInstances()
 })
 
-const it = testEffect(CrossSpawnSpawner.defaultLayer)
+const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, AppFileSystem.defaultLayer))
 
 function withTmp(
   init: (dir: string) => Promise,
@@ -837,7 +837,7 @@ describe("plugin.loader.shared", () => {
         Effect.gen(function* () {
           yield* load(tmp.path)
           expect(
-            yield* Effect.promise(() => Filesystem.readJson<{ source: string; enabled: boolean }>(tmp.extra.mark)),
+            (yield* (yield* AppFileSystem.Service).readJson(tmp.extra.mark)) as { source: string; enabled: boolean },
           ).toEqual({
             source: "tuple",
             enabled: true,
@@ -960,7 +960,8 @@ export default {
       (tmp) =>
         Effect.gen(function* () {
           const file = path.join(tmp.extra.mod, "package.json")
-          const json = yield* Effect.promise(() => Filesystem.readJson>(file))
+          const fsys = yield* AppFileSystem.Service
+          const json = (yield* fsys.readJson(file)) as Record
           const list = readPackageThemes("acme-plugin", {
             dir: tmp.extra.mod,
             pkg: file,
@@ -968,8 +969,8 @@ export default {
           })
 
           expect(list).toEqual([
-            Filesystem.resolve(path.join(tmp.extra.mod, "themes", "one.json")),
-            Filesystem.resolve(path.join(tmp.extra.mod, "themes", "two.json")),
+            AppFileSystem.resolve(path.join(tmp.extra.mod, "themes", "one.json")),
+            AppFileSystem.resolve(path.join(tmp.extra.mod, "themes", "two.json")),
           ])
         }),
     ),
@@ -1033,7 +1034,7 @@ export default {
               {
                 spec: "acme-plugin@1.0.0",
                 target: tmp.extra.mod,
-                themes: [Filesystem.resolve(path.join(tmp.extra.mod, "themes", "night.json"))],
+                themes: [AppFileSystem.resolve(path.join(tmp.extra.mod, "themes", "night.json"))],
               },
             ])
             expect(missing).toHaveLength(0)
@@ -1096,7 +1097,7 @@ export default {
             expect(loaded).toEqual([
               {
                 spec: "acme-plugin@1.0.0",
-                themes: [Filesystem.resolve(path.join(tmp.extra.mod, "themes", "night.json"))],
+                themes: [AppFileSystem.resolve(path.join(tmp.extra.mod, "themes", "night.json"))],
               },
             ])
           } finally {
@@ -1117,7 +1118,8 @@ export default {
       },
       (tmp) =>
         Effect.gen(function* () {
-          const json = yield* Effect.promise(() => Filesystem.readJson>(tmp.extra.file))
+          const fsys = yield* AppFileSystem.Service
+          const json = (yield* fsys.readJson(tmp.extra.file)) as Record
           expect(() =>
             readPackageThemes("acme", {
               dir: tmp.extra.mod,
diff --git a/packages/opencode/test/plugin/workspace-adapter.test.ts b/packages/opencode/test/plugin/workspace-adapter.test.ts
index 0cf603fa3..31cdf45bf 100644
--- a/packages/opencode/test/plugin/workspace-adapter.test.ts
+++ b/packages/opencode/test/plugin/workspace-adapter.test.ts
@@ -56,6 +56,7 @@ const workspaceLayer = Workspace.layer.pipe(
   Layer.provide(Project.defaultLayer),
   Layer.provide(Vcs.defaultLayer),
   Layer.provide(FetchHttpClient.layer),
+  Layer.provide(AppFileSystem.defaultLayer),
   Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrapLayer))),
   Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: true })),
 )
diff --git a/packages/opencode/test/tool/shell.test.ts b/packages/opencode/test/tool/shell.test.ts
index 6ce0e5c08..26c04e6be 100644
--- a/packages/opencode/test/tool/shell.test.ts
+++ b/packages/opencode/test/tool/shell.test.ts
@@ -1195,7 +1195,7 @@ describe("tool.shell truncation", () => {
         const filepath = (result.metadata as { outputPath?: string }).outputPath
         expect(filepath).toBeTruthy()
 
-        const saved = yield* Effect.promise(() => Filesystem.readText(filepath!))
+        const saved = yield* (yield* AppFileSystem.Service).readFileString(filepath!)
         const lines = saved.trim().split(/\r?\n/)
         expect(lines.length).toBe(lineCount)
         expect(lines[0]).toBe("1")
diff --git a/packages/opencode/test/tool/truncation.test.ts b/packages/opencode/test/tool/truncation.test.ts
index e948a6dcb..804bbd672 100644
--- a/packages/opencode/test/tool/truncation.test.ts
+++ b/packages/opencode/test/tool/truncation.test.ts
@@ -1,11 +1,11 @@
 import { describe, test, expect } from "bun:test"
 import { NodeFileSystem } from "@effect/platform-node"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { Effect, FileSystem, Layer } from "effect"
 import { Truncate } from "@/tool/truncate"
 import { Config } from "@/config/config"
 import { Identifier } from "../../src/id/id"
 import { Process } from "@/util/process"
-import { Filesystem } from "@/util/filesystem"
 import path from "path"
 import { testEffect } from "../lib/effect"
 import { writeFileStringScoped } from "../lib/filesystem"
@@ -14,10 +14,15 @@ import { TestConfig } from "../fixture/config"
 const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
 const ROOT = path.resolve(import.meta.dir, "..", "..")
 
-const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer))
+const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer, AppFileSystem.defaultLayer))
 
 const configuredLayer = (cfg: Config.Info) =>
-  Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer, TestConfig.layer({ get: () => Effect.succeed(cfg) }))
+  Layer.mergeAll(
+    Truncate.defaultLayer,
+    NodeFileSystem.layer,
+    AppFileSystem.defaultLayer,
+    TestConfig.layer({ get: () => Effect.succeed(cfg) }),
+  )
 const configuredIt = (cfg: Config.Info) => testEffect(configuredLayer(cfg))
 
 describe("Truncate", () => {
@@ -25,7 +30,8 @@ describe("Truncate", () => {
     it.live("truncates large json file by bytes", () =>
       Effect.gen(function* () {
         const svc = yield* Truncate.Service
-        const content = yield* Effect.promise(() => Filesystem.readText(path.join(FIXTURES_DIR, "models-api.json")))
+        const fsys = yield* AppFileSystem.Service
+        const content = yield* fsys.readFileString(path.join(FIXTURES_DIR, "models-api.json"))
         const result = yield* svc.output(content)
 
         expect(result.truncated).toBe(true)
@@ -158,7 +164,8 @@ describe("Truncate", () => {
     it.live("large single-line file truncates with byte message", () =>
       Effect.gen(function* () {
         const svc = yield* Truncate.Service
-        const content = yield* Effect.promise(() => Filesystem.readText(path.join(FIXTURES_DIR, "models-api.json")))
+        const fsys = yield* AppFileSystem.Service
+        const content = yield* fsys.readFileString(path.join(FIXTURES_DIR, "models-api.json"))
         const result = yield* svc.output(content)
 
         expect(result.truncated).toBe(true)
@@ -180,7 +187,8 @@ describe("Truncate", () => {
         expect(result.outputPath).toBeDefined()
         expect(result.outputPath).toContain("tool_")
 
-        const written = yield* Effect.promise(() => Filesystem.readText(result.outputPath!))
+        const fsys = yield* AppFileSystem.Service
+        const written = yield* fsys.readFileString(result.outputPath!)
         expect(written).toBe(lines)
       }),
     )

From 42e6b7d54147fd54aef59ad375a522c4245c1235 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 20:31:03 -0400
Subject: [PATCH 304/378] effect(core): track stderr truncation; polish
 AppProcess callers (#27353)

---
 packages/core/src/process.ts                |  6 ++++--
 packages/core/test/process/process.test.ts  | 24 ++++++++++++++++++---
 packages/opencode/src/format/index.ts       |  3 ++-
 packages/opencode/src/git/index.ts          |  2 +-
 packages/opencode/src/installation/index.ts |  3 ++-
 5 files changed, 30 insertions(+), 8 deletions(-)

diff --git a/packages/core/src/process.ts b/packages/core/src/process.ts
index 76ea9cf3f..f076ea4e4 100644
--- a/packages/core/src/process.ts
+++ b/packages/core/src/process.ts
@@ -31,7 +31,8 @@ export interface RunResult {
   readonly exitCode: number
   readonly stdout: Buffer
   readonly stderr: Buffer
-  readonly truncated: boolean
+  readonly stdoutTruncated: boolean
+  readonly stderrTruncated: boolean
 }
 
 export type Interface = ChildProcessSpawner["Service"] & {
@@ -147,7 +148,8 @@ export const layer = Layer.effect(
             exitCode,
             stdout: stdout.buffer,
             stderr: stderr.buffer,
-            truncated: stdout.truncated,
+            stdoutTruncated: stdout.truncated,
+            stderrTruncated: stderr.truncated,
           } satisfies RunResult
         }),
       )
diff --git a/packages/core/test/process/process.test.ts b/packages/core/test/process/process.test.ts
index 5cc73e616..49409ff9d 100644
--- a/packages/core/test/process/process.test.ts
+++ b/packages/core/test/process/process.test.ts
@@ -20,7 +20,8 @@ describe("AppProcess", () => {
         const result = yield* svc.run(cmd("-e", "process.stdout.write('hi\\n')"))
         expect(result.exitCode).toBe(0)
         expect(result.stdout.toString("utf8")).toBe("hi\n")
-        expect(result.truncated).toBe(false)
+        expect(result.stdoutTruncated).toBe(false)
+        expect(result.stderrTruncated).toBe(false)
       }),
     )
 
@@ -84,17 +85,34 @@ describe("AppProcess", () => {
     )
 
     it.effect(
-      "truncates output when maxOutputBytes is set",
+      "truncates stdout when maxOutputBytes is set",
       Effect.gen(function* () {
         const svc = yield* AppProcess.Service
         const result = yield* svc.run(cmd("-e", "process.stdout.write('0123456789')"), { maxOutputBytes: 5 })
         expect(result.exitCode).toBe(0)
-        expect(result.truncated).toBe(true)
+        expect(result.stdoutTruncated).toBe(true)
+        expect(result.stderrTruncated).toBe(false)
         expect(result.stdout.length).toBe(5)
         expect(result.stdout.toString("utf8")).toBe("01234")
       }),
     )
 
+    it.effect(
+      "truncates stderr when maxErrorBytes is set",
+      Effect.gen(function* () {
+        const svc = yield* AppProcess.Service
+        const result = yield* svc.run(
+          cmd("-e", "process.stderr.write('0123456789')"),
+          { maxErrorBytes: 5 },
+        )
+        expect(result.exitCode).toBe(0)
+        expect(result.stdoutTruncated).toBe(false)
+        expect(result.stderrTruncated).toBe(true)
+        expect(result.stderr.length).toBe(5)
+        expect(result.stderr.toString("utf8")).toBe("01234")
+      }),
+    )
+
     it.effect(
       "result includes command description",
       Effect.gen(function* () {
diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts
index d8365e0fa..fa2201443 100644
--- a/packages/opencode/src/format/index.ts
+++ b/packages/opencode/src/format/index.ts
@@ -5,6 +5,7 @@ import { InstanceState } from "@/effect/instance-state"
 import path from "path"
 import { mergeDeep } from "remeda"
 import { Config } from "@/config/config"
+import { errorMessage } from "@/util/error"
 import * as Log from "@opencode-ai/core/util/log"
 import * as Formatter from "./formatter"
 
@@ -100,7 +101,7 @@ export const layer = Layer.effect(
                         command: cmd,
                         ...item.environment,
                         file: filepath,
-                        cause: error.message,
+                        cause: errorMessage(error.cause ?? error),
                       })
                       return undefined
                     }),
diff --git a/packages/opencode/src/git/index.ts b/packages/opencode/src/git/index.ts
index 4c88edaea..5e76b7f73 100644
--- a/packages/opencode/src/git/index.ts
+++ b/packages/opencode/src/git/index.ts
@@ -124,7 +124,7 @@ export const layer = Layer.effect(
           text: () => result.stdout.toString("utf8"),
           stdout: result.stdout,
           stderr: result.stderr,
-          truncated: result.truncated,
+          truncated: result.stdoutTruncated || result.stderrTruncated,
         } satisfies Result
       },
       Effect.catch((err) => Effect.succeed(fail(err))),
diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts
index 00fbabe35..9b0e06c4a 100644
--- a/packages/opencode/src/installation/index.ts
+++ b/packages/opencode/src/installation/index.ts
@@ -1,6 +1,7 @@
 import { Effect, Layer, Schema, Context, Stream } from "effect"
 import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
 import { withTransientReadRetry } from "@/util/effect-http-client"
+import { errorMessage } from "@/util/error"
 import { ChildProcess } from "effect/unstable/process"
 import { AppProcess } from "@opencode-ai/core/process"
 import path from "path"
@@ -124,7 +125,7 @@ export const layer: Layer.Layer Effect.succeed({ code: 1, stdout: "", stderr: "" })),
+      Effect.catch((err) => Effect.succeed({ code: 1, stdout: "", stderr: errorMessage(err) })),
     )
 
     const getBrewFormula = Effect.fnUntraced(function* () {

From 3f33be19289de84608adc487ec5f57075238d59b Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 20:31:27 -0400
Subject: [PATCH 305/378] effect(server): typed errors in session/sync
 handlers, fix concurrency (#27146)

---
 .../routes/instance/httpapi/handlers/instance.ts |  4 +++-
 .../routes/instance/httpapi/handlers/session.ts  | 16 ++++++++--------
 .../routes/instance/httpapi/handlers/sync.ts     |  4 ++--
 3 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/instance.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/instance.ts
index 50a7fecfa..4ae318ef2 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/instance.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/instance.ts
@@ -38,7 +38,9 @@ export const instanceHandlers = HttpApiBuilder.group(InstanceHttpApi, "instance"
     })
 
     const getVcs = Effect.fn("InstanceHttpApi.vcs")(function* () {
-      const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 })
+      const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], {
+        concurrency: "unbounded",
+      })
       return { branch, default_branch }
     })
 
diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
index df2b40de5..1fb2455ab 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
@@ -36,6 +36,12 @@ import {
 } from "../groups/session"
 import * as SessionError from "./session-errors"
 
+const tryParseJson = (text: string) =>
+  Effect.try({
+    try: () => JSON.parse(text) as unknown,
+    catch: () => new HttpApiError.BadRequest({}),
+  })
+
 export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", (handlers) =>
   Effect.gen(function* () {
     const session = yield* Session.Service
@@ -160,10 +166,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       const body = yield* Effect.orDie(ctx.request.text)
       if (body.trim().length === 0) return yield* create({})
 
-      const json = yield* Effect.try({
-        try: () => JSON.parse(body) as unknown,
-        catch: () => new HttpApiError.BadRequest({}),
-      })
+      const json = yield* tryParseJson(body)
       const payload = yield* Schema.decodeUnknownEffect(Session.CreateInput)(json).pipe(
         Effect.mapError(() => new HttpApiError.BadRequest({})),
       )
@@ -211,10 +214,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       const body = yield* Effect.orDie(ctx.request.text)
       if (body.trim().length === 0) return yield* fork({ params: ctx.params })
 
-      const json = yield* Effect.try({
-        try: () => JSON.parse(body) as unknown,
-        catch: () => new HttpApiError.BadRequest({}),
-      })
+      const json = yield* tryParseJson(body)
       const payload = yield* Schema.decodeUnknownEffect(ForkPayload)(json).pipe(
         Effect.mapError(() => new HttpApiError.BadRequest({})),
       )
diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts
index 152d22f98..ffe8d0baa 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts
@@ -11,7 +11,7 @@ import { lte } from "drizzle-orm"
 import { not } from "drizzle-orm"
 import { or } from "drizzle-orm"
 import { Effect, Scope } from "effect"
-import { HttpApiBuilder } from "effect/unstable/httpapi"
+import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi"
 import { InstanceHttpApi } from "../api"
 import { HistoryPayload, ReplayPayload, SessionPayload } from "../groups/sync"
 import * as Log from "@opencode-ai/core/util/log"
@@ -59,7 +59,7 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl
 
     const steal = Effect.fn("SyncHttpApi.steal")(function* (ctx: { payload: typeof SessionPayload.Type }) {
       const workspaceID = yield* InstanceState.workspaceID
-      if (!workspaceID) throw new Error("Cannot steal session without workspace context")
+      if (!workspaceID) return yield* new HttpApiError.BadRequest({})
 
       yield* sync.run(Session.Event.Updated, {
         sessionID: ctx.payload.sessionID,

From aa8a41d1b8f629e4d87f0a5334c3052d7ea7b27d Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 20:32:19 -0400
Subject: [PATCH 306/378] effect(patch,tool): migrate patch/index and tool/read
 to AppFileSystem (#27155)

---
 packages/opencode/specs/effect/tools.md    |   6 +-
 packages/opencode/src/patch/index.ts       | 146 +++++------
 packages/opencode/src/tool/apply_patch.ts  |   2 +-
 packages/opencode/src/tool/read.ts         |  95 ++++---
 packages/opencode/test/patch/patch.test.ts | 273 ++++++++++++---------
 5 files changed, 277 insertions(+), 245 deletions(-)

diff --git a/packages/opencode/specs/effect/tools.md b/packages/opencode/specs/effect/tools.md
index 37a76e948..b8c851aa3 100644
--- a/packages/opencode/specs/effect/tools.md
+++ b/packages/opencode/specs/effect/tools.md
@@ -67,11 +67,11 @@ Most exported tools are already on the intended Effect-native shape. The remaini
 
 Current spot cleanups worth tracking:
 
-- [ ] `read.ts` — still bridges to Node stream / `readline` helpers and Promise-based binary detection
+- [x] `read.ts` — streams through `AppFileSystem.Service.stream` with `Stream.splitLines`; the legacy Node stream / `readline` helper is gone
 - [ ] `bash.ts` — already uses Effect child-process primitives; only keep tracking shell-specific platform bridges and parser/loading details as they come up
 - [ ] `webfetch.ts` — already uses `HttpClient`; remaining work is limited to smaller boundary helpers like HTML text extraction
 - [ ] `file/ripgrep.ts` — adjacent to tool migration; still has raw fs/process usage that affects `grep.ts` and file-search routes
-- [ ] `patch/index.ts` — adjacent to tool migration; still has raw fs usage behind patch application
+- [x] `patch/index.ts` — apply path now returns `Effect` over `AppFileSystem.Service`; the parser and chunk replacer stay pure
 
 Notable items that are already effectively on the target path and do not need separate migration bullets right now:
 
@@ -85,6 +85,4 @@ Notable items that are already effectively on the target path and do not need se
 
 Current raw fs users that still appear relevant here:
 
-- `tool/read.ts` — `fs.createReadStream`, `readline`
 - `file/ripgrep.ts` — `fs/promises`
-- `patch/index.ts` — `fs`, `fs/promises`
diff --git a/packages/opencode/src/patch/index.ts b/packages/opencode/src/patch/index.ts
index 3dfa6f2d0..48c616d9b 100644
--- a/packages/opencode/src/patch/index.ts
+++ b/packages/opencode/src/patch/index.ts
@@ -1,7 +1,6 @@
-import { Schema } from "effect"
+import { Effect, Schema } from "effect"
 import * as path from "path"
-import * as fs from "fs/promises"
-import { readFileSync } from "fs"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import * as Log from "@opencode-ai/core/util/log"
 import * as Bom from "../util/bom"
 
@@ -308,14 +307,12 @@ interface ApplyPatchFileUpdate {
   bom: boolean
 }
 
-export function deriveNewContentsFromChunks(filePath: string, chunks: UpdateFileChunk[]): ApplyPatchFileUpdate {
-  // Read original file content
-  let originalContent: ReturnType
-  try {
-    originalContent = Bom.split(readFileSync(filePath, "utf-8"))
-  } catch (error) {
-    throw new Error(`Failed to read file ${filePath}: ${error}`, { cause: error })
-  }
+export function deriveNewContentsFromChunks(
+  filePath: string,
+  chunks: UpdateFileChunk[],
+  originalText: string,
+): ApplyPatchFileUpdate {
+  const originalContent = Bom.split(originalText)
 
   let originalLines = originalContent.text.split("\n")
 
@@ -423,11 +420,11 @@ function applyReplacements(lines: string[], replacements: Array<[number, number,
 // Normalize Unicode punctuation to ASCII equivalents (like Rust's normalize_unicode)
 function normalizeUnicode(str: string): string {
   return str
-    .replace(/[\u2018\u2019\u201A\u201B]/g, "'") // single quotes
-    .replace(/[\u201C\u201D\u201E\u201F]/g, '"') // double quotes
-    .replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/g, "-") // dashes
-    .replace(/\u2026/g, "...") // ellipsis
-    .replace(/\u00A0/g, " ") // non-breaking space
+    .replace(/[‘’‚‛]/g, "'") // single quotes
+    .replace(/[“”„‟]/g, '"') // double quotes
+    .replace(/[‐‑‒–—―]/g, "-") // dashes
+    .replace(/…/g, "...") // ellipsis
+    .replace(/ /g, " ") // non-breaking space
 }
 
 type Comparator = (a: string, b: string) => boolean
@@ -517,77 +514,71 @@ function generateUnifiedDiff(oldContent: string, newContent: string): string {
 }
 
 // Apply hunks to filesystem
-export async function applyHunksToFiles(hunks: Hunk[]): Promise {
+export const applyHunksToFiles = Effect.fn("Patch.applyHunksToFiles")(function* (hunks: Hunk[]) {
   if (hunks.length === 0) {
-    throw new Error("No files were modified.")
+    return yield* Effect.fail(new Error("No files were modified."))
   }
 
+  const fs = yield* AppFileSystem.Service
+
   const added: string[] = []
   const modified: string[] = []
   const deleted: string[] = []
 
   for (const hunk of hunks) {
     switch (hunk.type) {
-      case "add":
-        // Create parent directories
-        const addDir = path.dirname(hunk.path)
-        if (addDir !== "." && addDir !== "/") {
-          await fs.mkdir(addDir, { recursive: true })
-        }
-
-        await fs.writeFile(hunk.path, hunk.contents, "utf-8")
+      case "add": {
+        yield* fs.writeWithDirs(hunk.path, hunk.contents)
         added.push(hunk.path)
         log.info(`Added file: ${hunk.path}`)
         break
+      }
 
-      case "delete":
-        await fs.unlink(hunk.path)
+      case "delete": {
+        yield* fs.remove(hunk.path)
         deleted.push(hunk.path)
         log.info(`Deleted file: ${hunk.path}`)
         break
+      }
 
-      case "update":
-        const fileUpdate = deriveNewContentsFromChunks(hunk.path, hunk.chunks)
+      case "update": {
+        const originalText = yield* fs.readFileString(hunk.path)
+        const fileUpdate = deriveNewContentsFromChunks(hunk.path, hunk.chunks, originalText)
 
         if (hunk.move_path) {
-          // Handle file move
-          const moveDir = path.dirname(hunk.move_path)
-          if (moveDir !== "." && moveDir !== "/") {
-            await fs.mkdir(moveDir, { recursive: true })
-          }
-
-          await fs.writeFile(hunk.move_path, Bom.join(fileUpdate.content, fileUpdate.bom), "utf-8")
-          await fs.unlink(hunk.path)
+          yield* fs.writeWithDirs(hunk.move_path, Bom.join(fileUpdate.content, fileUpdate.bom))
+          yield* fs.remove(hunk.path)
           modified.push(hunk.move_path)
           log.info(`Moved file: ${hunk.path} -> ${hunk.move_path}`)
         } else {
-          // Regular update
-          await fs.writeFile(hunk.path, Bom.join(fileUpdate.content, fileUpdate.bom), "utf-8")
+          yield* fs.writeWithDirs(hunk.path, Bom.join(fileUpdate.content, fileUpdate.bom))
           modified.push(hunk.path)
           log.info(`Updated file: ${hunk.path}`)
         }
         break
+      }
     }
   }
 
-  return { added, modified, deleted }
-}
+  return { added, modified, deleted } satisfies AffectedPaths
+})
 
 // Main patch application function
-export async function applyPatch(patchText: string): Promise {
+export const applyPatch = Effect.fn("Patch.applyPatch")(function* (patchText: string) {
   const { hunks } = parsePatch(patchText)
-  return applyHunksToFiles(hunks)
-}
+  return yield* applyHunksToFiles(hunks)
+})
 
-// Async version of maybeParseApplyPatchVerified
-export async function maybeParseApplyPatchVerified(
-  argv: string[],
-  cwd: string,
-): Promise<
+type MaybeApplyPatchVerifiedResult =
   | { type: MaybeApplyPatchVerified.Body; action: ApplyPatchAction }
   | { type: MaybeApplyPatchVerified.CorrectnessError; error: Error }
   | { type: MaybeApplyPatchVerified.NotApplyPatch }
-> {
+
+// Effectful verified-parse: needs AppFileSystem.Service to read existing files
+export const maybeParseApplyPatchVerified = Effect.fn("Patch.maybeParseApplyPatchVerified")(function* (
+  argv: string[],
+  cwd: string,
+) {
   // Detect implicit patch invocation (raw patch without apply_patch command)
   if (argv.length === 1) {
     try {
@@ -595,7 +586,7 @@ export async function maybeParseApplyPatchVerified(
       return {
         type: MaybeApplyPatchVerified.CorrectnessError,
         error: new Error(ApplyPatchError.ImplicitInvocation),
-      }
+      } satisfies MaybeApplyPatchVerifiedResult
     } catch {
       // Not a patch, continue
     }
@@ -604,8 +595,9 @@ export async function maybeParseApplyPatchVerified(
   const result = maybeParseApplyPatch(argv)
 
   switch (result.type) {
-    case MaybeApplyPatch.Body:
-      const { args } = result
+    case MaybeApplyPatch.Body: {
+      const fs = yield* AppFileSystem.Service
+      const args = result.args
       const effectiveCwd = args.workdir ? path.resolve(cwd, args.workdir) : cwd
       const changes = new Map()
 
@@ -623,27 +615,37 @@ export async function maybeParseApplyPatchVerified(
             })
             break
 
-          case "delete":
-            // For delete, we need to read the current content
+          case "delete": {
             const deletePath = path.resolve(effectiveCwd, hunk.path)
-            try {
-              const content = await fs.readFile(deletePath, "utf-8")
-              changes.set(resolvedPath, {
-                type: "delete",
-                content,
-              })
-            } catch {
+            const content = yield* fs.readFileString(deletePath).pipe(Effect.catch(() => Effect.succeed(undefined)))
+            if (content === undefined) {
               return {
                 type: MaybeApplyPatchVerified.CorrectnessError,
                 error: new Error(`Failed to read file for deletion: ${deletePath}`),
-              }
+              } satisfies MaybeApplyPatchVerifiedResult
             }
+            changes.set(resolvedPath, {
+              type: "delete",
+              content,
+            })
             break
+          }
 
-          case "update":
+          case "update": {
             const updatePath = path.resolve(effectiveCwd, hunk.path)
+            const originalText = yield* fs.readFileString(updatePath).pipe(
+              Effect.catch((cause) =>
+                Effect.succeed(new Error(`Failed to read file ${updatePath}: ${cause}`, { cause })),
+              ),
+            )
+            if (originalText instanceof Error) {
+              return {
+                type: MaybeApplyPatchVerified.CorrectnessError,
+                error: originalText,
+              } satisfies MaybeApplyPatchVerifiedResult
+            }
             try {
-              const fileUpdate = deriveNewContentsFromChunks(updatePath, hunk.chunks)
+              const fileUpdate = deriveNewContentsFromChunks(updatePath, hunk.chunks, originalText)
               changes.set(resolvedPath, {
                 type: "update",
                 unified_diff: fileUpdate.unified_diff,
@@ -654,9 +656,10 @@ export async function maybeParseApplyPatchVerified(
               return {
                 type: MaybeApplyPatchVerified.CorrectnessError,
                 error: error as Error,
-              }
+              } satisfies MaybeApplyPatchVerifiedResult
             }
             break
+          }
         }
       }
 
@@ -667,17 +670,18 @@ export async function maybeParseApplyPatchVerified(
           patch: args.patch,
           cwd: effectiveCwd,
         },
-      }
+      } satisfies MaybeApplyPatchVerifiedResult
+    }
 
     case MaybeApplyPatch.PatchParseError:
       return {
         type: MaybeApplyPatchVerified.CorrectnessError,
         error: result.error,
-      }
+      } satisfies MaybeApplyPatchVerifiedResult
 
     case MaybeApplyPatch.NotApplyPatch:
-      return { type: MaybeApplyPatchVerified.NotApplyPatch }
+      return { type: MaybeApplyPatchVerified.NotApplyPatch } satisfies MaybeApplyPatchVerifiedResult
   }
-}
+})
 
 export * as Patch from "."
diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts
index 916e11f1e..c5d9c57dd 100644
--- a/packages/opencode/src/tool/apply_patch.ts
+++ b/packages/opencode/src/tool/apply_patch.ts
@@ -119,7 +119,7 @@ export const ApplyPatchTool = Tool.define(
 
             // Apply the update chunks to get new content
             try {
-              const fileUpdate = Patch.deriveNewContentsFromChunks(filePath, hunk.chunks)
+              const fileUpdate = Patch.deriveNewContentsFromChunks(filePath, hunk.chunks, Bom.join(source.text, source.bom))
               newContent = fileUpdate.content
               bom = fileUpdate.bom
             } catch (error) {
diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts
index ad3c33e74..8a1b64fec 100644
--- a/packages/opencode/src/tool/read.ts
+++ b/packages/opencode/src/tool/read.ts
@@ -1,8 +1,6 @@
-import { Effect, Option, Schema, Scope } from "effect"
+import { Effect, Option, Schema, Scope, Stream } from "effect"
 import { NonNegativeInt } from "@opencode-ai/core/schema"
-import { createReadStream } from "fs"
 import * as path from "path"
-import { createInterface } from "readline"
 import * as Tool from "./tool"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { LSP } from "@/lsp/lsp"
@@ -105,6 +103,49 @@ export const ReadTool = Tool.define(
       )
     })
 
+    const lines = Effect.fn("ReadTool.lines")(function* (filepath: string, opts: { limit: number; offset: number }) {
+      const start = opts.offset - 1
+      const raw: string[] = []
+      const flags = { bytes: 0, count: 0, cut: false, more: false, done: false }
+
+      // Note: prefer manual TextDecoder over Stream.decodeText — when the source stream
+      // ends without flushing, decodeText drops the final unterminated line. We also
+      // avoid Stream.runForEachWhile (it currently swallows the final unterminated
+      // line of the upstream splitLines pipeline) and instead toggle a `done` flag
+      // and ignore subsequent lines.
+      const decoder = new TextDecoder("utf-8")
+      yield* fs.stream(filepath).pipe(
+        Stream.map((bytes) => decoder.decode(bytes, { stream: true })),
+        Stream.splitLines,
+        Stream.runForEach((text) =>
+          Effect.sync(() => {
+            if (flags.done) return
+            flags.count += 1
+            if (flags.count <= start) return
+
+            if (raw.length >= opts.limit) {
+              flags.more = true
+              return
+            }
+
+            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 (flags.bytes + size > MAX_BYTES) {
+              flags.cut = true
+              flags.more = true
+              flags.done = true
+              return
+            }
+
+            raw.push(line)
+            flags.bytes += size
+          }),
+        ),
+      )
+
+      return { raw, count: flags.count, cut: flags.cut, more: flags.more, offset: opts.offset }
+    })
+
     const isBinaryFile = (filepath: string, bytes: Uint8Array) => {
       const ext = path.extname(filepath).toLowerCase()
       switch (ext) {
@@ -247,9 +288,7 @@ export const ReadTool = Tool.define(
         return yield* Effect.fail(new Error(`Cannot read binary file: ${filepath}`))
       }
 
-      const file = yield* Effect.promise(() =>
-        lines(filepath, { limit: params.limit ?? DEFAULT_READ_LIMIT, offset: params.offset || 1 }),
-      )
+      const file = yield* 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)`),
@@ -296,47 +335,3 @@ export const ReadTool = Tool.define(
     }
   }),
 )
-
-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 }
-}
diff --git a/packages/opencode/test/patch/patch.test.ts b/packages/opencode/test/patch/patch.test.ts
index 020253bfe..e4952b9e0 100644
--- a/packages/opencode/test/patch/patch.test.ts
+++ b/packages/opencode/test/patch/patch.test.ts
@@ -1,8 +1,13 @@
 import { describe, test, expect, beforeEach, afterEach } from "bun:test"
-import { Patch } from "../../src/patch"
+import { Effect } from "effect"
 import * as fs from "fs/promises"
 import * as path from "path"
 import { tmpdir } from "os"
+import { Patch } from "../../src/patch"
+import { AppFileSystem } from "@opencode-ai/core/filesystem"
+import { testEffect } from "../lib/effect"
+
+const it = testEffect(AppFileSystem.defaultLayer)
 
 describe("Patch namespace", () => {
   let tempDir: string
@@ -134,46 +139,53 @@ PATCH`
   })
 
   describe("applyPatch", () => {
-    test("should add a new file", async () => {
-      const patchText = `*** Begin Patch
+    it.live("should add a new file", () =>
+      Effect.gen(function* () {
+        const patchText = `*** Begin Patch
 *** Add File: ${tempDir}/new-file.txt
 +Hello World
 +This is a new file
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.added).toHaveLength(1)
-      expect(result.modified).toHaveLength(0)
-      expect(result.deleted).toHaveLength(0)
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.added).toHaveLength(1)
+        expect(result.modified).toHaveLength(0)
+        expect(result.deleted).toHaveLength(0)
 
-      const content = await fs.readFile(result.added[0], "utf-8")
-      expect(content).toBe("Hello World\nThis is a new file")
-    })
+        const content = yield* Effect.promise(() => fs.readFile(result.added[0], "utf-8"))
+        expect(content).toBe("Hello World\nThis is a new file")
+      }),
+    )
 
-    test("should delete an existing file", async () => {
-      const filePath = path.join(tempDir, "to-delete.txt")
-      await fs.writeFile(filePath, "This file will be deleted")
+    it.live("should delete an existing file", () =>
+      Effect.gen(function* () {
+        const filePath = path.join(tempDir, "to-delete.txt")
+        yield* Effect.promise(() => fs.writeFile(filePath, "This file will be deleted"))
 
-      const patchText = `*** Begin Patch
+        const patchText = `*** Begin Patch
 *** Delete File: ${filePath}
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.deleted).toHaveLength(1)
-      expect(result.deleted[0]).toBe(filePath)
-
-      const exists = await fs
-        .access(filePath)
-        .then(() => true)
-        .catch(() => false)
-      expect(exists).toBe(false)
-    })
-
-    test("should update an existing file", async () => {
-      const filePath = path.join(tempDir, "to-update.txt")
-      await fs.writeFile(filePath, "line 1\nline 2\nline 3\n")
-
-      const patchText = `*** Begin Patch
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.deleted).toHaveLength(1)
+        expect(result.deleted[0]).toBe(filePath)
+
+        const exists = yield* Effect.promise(() =>
+          fs
+            .access(filePath)
+            .then(() => true)
+            .catch(() => false),
+        )
+        expect(exists).toBe(false)
+      }),
+    )
+
+    it.live("should update an existing file", () =>
+      Effect.gen(function* () {
+        const filePath = path.join(tempDir, "to-update.txt")
+        yield* Effect.promise(() => fs.writeFile(filePath, "line 1\nline 2\nline 3\n"))
+
+        const patchText = `*** Begin Patch
 *** Update File: ${filePath}
 @@
  line 1
@@ -182,20 +194,22 @@ PATCH`
  line 3
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.modified).toHaveLength(1)
-      expect(result.modified[0]).toBe(filePath)
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.modified).toHaveLength(1)
+        expect(result.modified[0]).toBe(filePath)
 
-      const content = await fs.readFile(filePath, "utf-8")
-      expect(content).toBe("line 1\nline 2 updated\nline 3\n")
-    })
+        const content = yield* Effect.promise(() => fs.readFile(filePath, "utf-8"))
+        expect(content).toBe("line 1\nline 2 updated\nline 3\n")
+      }),
+    )
 
-    test("should move and update a file", async () => {
-      const oldPath = path.join(tempDir, "old-name.txt")
-      const newPath = path.join(tempDir, "new-name.txt")
-      await fs.writeFile(oldPath, "old content\n")
+    it.live("should move and update a file", () =>
+      Effect.gen(function* () {
+        const oldPath = path.join(tempDir, "old-name.txt")
+        const newPath = path.join(tempDir, "new-name.txt")
+        yield* Effect.promise(() => fs.writeFile(oldPath, "old content\n"))
 
-      const patchText = `*** Begin Patch
+        const patchText = `*** Begin Patch
 *** Update File: ${oldPath}
 *** Move to: ${newPath}
 @@
@@ -203,29 +217,33 @@ PATCH`
 +new content
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.modified).toHaveLength(1)
-      expect(result.modified[0]).toBe(newPath)
-
-      const oldExists = await fs
-        .access(oldPath)
-        .then(() => true)
-        .catch(() => false)
-      expect(oldExists).toBe(false)
-
-      const newContent = await fs.readFile(newPath, "utf-8")
-      expect(newContent).toBe("new content\n")
-    })
-
-    test("should handle multiple operations in one patch", async () => {
-      const file1 = path.join(tempDir, "file1.txt")
-      const file2 = path.join(tempDir, "file2.txt")
-      const file3 = path.join(tempDir, "file3.txt")
-
-      await fs.writeFile(file1, "content 1")
-      await fs.writeFile(file2, "content 2")
-
-      const patchText = `*** Begin Patch
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.modified).toHaveLength(1)
+        expect(result.modified[0]).toBe(newPath)
+
+        const oldExists = yield* Effect.promise(() =>
+          fs
+            .access(oldPath)
+            .then(() => true)
+            .catch(() => false),
+        )
+        expect(oldExists).toBe(false)
+
+        const newContent = yield* Effect.promise(() => fs.readFile(newPath, "utf-8"))
+        expect(newContent).toBe("new content\n")
+      }),
+    )
+
+    it.live("should handle multiple operations in one patch", () =>
+      Effect.gen(function* () {
+        const file1 = path.join(tempDir, "file1.txt")
+        const file2 = path.join(tempDir, "file2.txt")
+        const file3 = path.join(tempDir, "file3.txt")
+
+        yield* Effect.promise(() => fs.writeFile(file1, "content 1"))
+        yield* Effect.promise(() => fs.writeFile(file2, "content 2"))
+
+        const patchText = `*** Begin Patch
 *** Add File: ${file3}
 +new file content
 *** Update File: ${file1}
@@ -235,98 +253,114 @@ PATCH`
 *** Delete File: ${file2}
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.added).toHaveLength(1)
-      expect(result.modified).toHaveLength(1)
-      expect(result.deleted).toHaveLength(1)
-    })
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.added).toHaveLength(1)
+        expect(result.modified).toHaveLength(1)
+        expect(result.deleted).toHaveLength(1)
+      }),
+    )
 
-    test("should create parent directories when adding files", async () => {
-      const nestedPath = path.join(tempDir, "deep", "nested", "file.txt")
+    it.live("should create parent directories when adding files", () =>
+      Effect.gen(function* () {
+        const nestedPath = path.join(tempDir, "deep", "nested", "file.txt")
 
-      const patchText = `*** Begin Patch
+        const patchText = `*** Begin Patch
 *** Add File: ${nestedPath}
 +Deep nested content
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.added).toHaveLength(1)
-      expect(result.added[0]).toBe(nestedPath)
-
-      const exists = await fs
-        .access(nestedPath)
-        .then(() => true)
-        .catch(() => false)
-      expect(exists).toBe(true)
-    })
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.added).toHaveLength(1)
+        expect(result.added[0]).toBe(nestedPath)
+
+        const exists = yield* Effect.promise(() =>
+          fs
+            .access(nestedPath)
+            .then(() => true)
+            .catch(() => false),
+        )
+        expect(exists).toBe(true)
+      }),
+    )
   })
 
   describe("error handling", () => {
-    test("should throw error when updating non-existent file", async () => {
-      const nonExistent = path.join(tempDir, "does-not-exist.txt")
+    it.live("should fail when updating non-existent file", () =>
+      Effect.gen(function* () {
+        const nonExistent = path.join(tempDir, "does-not-exist.txt")
 
-      const patchText = `*** Begin Patch
+        const patchText = `*** Begin Patch
 *** Update File: ${nonExistent}
 @@
 -old line
 +new line
 *** End Patch`
 
-      await expect(Patch.applyPatch(patchText)).rejects.toThrow()
-    })
+        const exit = yield* Effect.exit(Patch.applyPatch(patchText))
+        expect(exit._tag).toBe("Failure")
+      }),
+    )
 
-    test("should throw error when deleting non-existent file", async () => {
-      const nonExistent = path.join(tempDir, "does-not-exist.txt")
+    it.live("should fail when deleting non-existent file", () =>
+      Effect.gen(function* () {
+        const nonExistent = path.join(tempDir, "does-not-exist.txt")
 
-      const patchText = `*** Begin Patch
+        const patchText = `*** Begin Patch
 *** Delete File: ${nonExistent}
 *** End Patch`
 
-      await expect(Patch.applyPatch(patchText)).rejects.toThrow()
-    })
+        const exit = yield* Effect.exit(Patch.applyPatch(patchText))
+        expect(exit._tag).toBe("Failure")
+      }),
+    )
   })
 
   describe("edge cases", () => {
-    test("should handle empty files", async () => {
-      const emptyFile = path.join(tempDir, "empty.txt")
-      await fs.writeFile(emptyFile, "")
+    it.live("should handle empty files", () =>
+      Effect.gen(function* () {
+        const emptyFile = path.join(tempDir, "empty.txt")
+        yield* Effect.promise(() => fs.writeFile(emptyFile, ""))
 
-      const patchText = `*** Begin Patch
+        const patchText = `*** Begin Patch
 *** Update File: ${emptyFile}
 @@
 +First line
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.modified).toHaveLength(1)
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.modified).toHaveLength(1)
 
-      const content = await fs.readFile(emptyFile, "utf-8")
-      expect(content).toBe("First line\n")
-    })
+        const content = yield* Effect.promise(() => fs.readFile(emptyFile, "utf-8"))
+        expect(content).toBe("First line\n")
+      }),
+    )
 
-    test("should handle files with no trailing newline", async () => {
-      const filePath = path.join(tempDir, "no-newline.txt")
-      await fs.writeFile(filePath, "no newline")
+    it.live("should handle files with no trailing newline", () =>
+      Effect.gen(function* () {
+        const filePath = path.join(tempDir, "no-newline.txt")
+        yield* Effect.promise(() => fs.writeFile(filePath, "no newline"))
 
-      const patchText = `*** Begin Patch
+        const patchText = `*** Begin Patch
 *** Update File: ${filePath}
 @@
 -no newline
 +has newline now
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.modified).toHaveLength(1)
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.modified).toHaveLength(1)
 
-      const content = await fs.readFile(filePath, "utf-8")
-      expect(content).toBe("has newline now\n")
-    })
+        const content = yield* Effect.promise(() => fs.readFile(filePath, "utf-8"))
+        expect(content).toBe("has newline now\n")
+      }),
+    )
 
-    test("should handle multiple update chunks in single file", async () => {
-      const filePath = path.join(tempDir, "multi-chunk.txt")
-      await fs.writeFile(filePath, "line 1\nline 2\nline 3\nline 4\n")
+    it.live("should handle multiple update chunks in single file", () =>
+      Effect.gen(function* () {
+        const filePath = path.join(tempDir, "multi-chunk.txt")
+        yield* Effect.promise(() => fs.writeFile(filePath, "line 1\nline 2\nline 3\nline 4\n"))
 
-      const patchText = `*** Begin Patch
+        const patchText = `*** Begin Patch
 *** Update File: ${filePath}
 @@
  line 1
@@ -338,11 +372,12 @@ PATCH`
 +LINE 4
 *** End Patch`
 
-      const result = await Patch.applyPatch(patchText)
-      expect(result.modified).toHaveLength(1)
+        const result = yield* Patch.applyPatch(patchText)
+        expect(result.modified).toHaveLength(1)
 
-      const content = await fs.readFile(filePath, "utf-8")
-      expect(content).toBe("line 1\nLINE 2\nline 3\nLINE 4\n")
-    })
+        const content = yield* Effect.promise(() => fs.readFile(filePath, "utf-8"))
+        expect(content).toBe("line 1\nLINE 2\nline 3\nLINE 4\n")
+      }),
+    )
   })
 })

From 10c90eb445879326bf0a45cfc098d2c9891b8e0d Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]" 
Date: Thu, 14 May 2026 00:33:32 +0000
Subject: [PATCH 307/378] chore: generate

---
 packages/core/test/process/process.test.ts |  5 +----
 packages/opencode/src/patch/index.ts       | 12 +++++++-----
 packages/opencode/src/tool/apply_patch.ts  |  6 +++++-
 3 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/packages/core/test/process/process.test.ts b/packages/core/test/process/process.test.ts
index 49409ff9d..f6a52b7a6 100644
--- a/packages/core/test/process/process.test.ts
+++ b/packages/core/test/process/process.test.ts
@@ -101,10 +101,7 @@ describe("AppProcess", () => {
       "truncates stderr when maxErrorBytes is set",
       Effect.gen(function* () {
         const svc = yield* AppProcess.Service
-        const result = yield* svc.run(
-          cmd("-e", "process.stderr.write('0123456789')"),
-          { maxErrorBytes: 5 },
-        )
+        const result = yield* svc.run(cmd("-e", "process.stderr.write('0123456789')"), { maxErrorBytes: 5 })
         expect(result.exitCode).toBe(0)
         expect(result.stdoutTruncated).toBe(false)
         expect(result.stderrTruncated).toBe(true)
diff --git a/packages/opencode/src/patch/index.ts b/packages/opencode/src/patch/index.ts
index 48c616d9b..42b26fe96 100644
--- a/packages/opencode/src/patch/index.ts
+++ b/packages/opencode/src/patch/index.ts
@@ -633,11 +633,13 @@ export const maybeParseApplyPatchVerified = Effect.fn("Patch.maybeParseApplyPatc
 
           case "update": {
             const updatePath = path.resolve(effectiveCwd, hunk.path)
-            const originalText = yield* fs.readFileString(updatePath).pipe(
-              Effect.catch((cause) =>
-                Effect.succeed(new Error(`Failed to read file ${updatePath}: ${cause}`, { cause })),
-              ),
-            )
+            const originalText = yield* fs
+              .readFileString(updatePath)
+              .pipe(
+                Effect.catch((cause) =>
+                  Effect.succeed(new Error(`Failed to read file ${updatePath}: ${cause}`, { cause })),
+                ),
+              )
             if (originalText instanceof Error) {
               return {
                 type: MaybeApplyPatchVerified.CorrectnessError,
diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts
index c5d9c57dd..84e84cc39 100644
--- a/packages/opencode/src/tool/apply_patch.ts
+++ b/packages/opencode/src/tool/apply_patch.ts
@@ -119,7 +119,11 @@ export const ApplyPatchTool = Tool.define(
 
             // Apply the update chunks to get new content
             try {
-              const fileUpdate = Patch.deriveNewContentsFromChunks(filePath, hunk.chunks, Bom.join(source.text, source.bom))
+              const fileUpdate = Patch.deriveNewContentsFromChunks(
+                filePath,
+                hunk.chunks,
+                Bom.join(source.text, source.bom),
+              )
               newContent = fileUpdate.content
               bom = fileUpdate.bom
             } catch (error) {

From ba5c8d38225267c33184561f377c9ca9d0afb1b6 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 20:43:32 -0400
Subject: [PATCH 308/378] fix(llm): preserve tool error defects (#27403)

---
 packages/llm/src/schema/errors.ts      |  1 +
 packages/llm/src/schema/events.ts      |  1 +
 packages/llm/src/tool-runtime.ts       | 36 +++++++++++++++++++-------
 packages/llm/test/tool-runtime.test.ts | 35 +++++++++++++++----------
 4 files changed, 49 insertions(+), 24 deletions(-)

diff --git a/packages/llm/src/schema/errors.ts b/packages/llm/src/schema/errors.ts
index 9bcc8e169..39bf5b625 100644
--- a/packages/llm/src/schema/errors.ts
+++ b/packages/llm/src/schema/errors.ts
@@ -198,5 +198,6 @@ export class LLMError extends Schema.TaggedErrorClass()("LLM.Error", {
  */
 export class ToolFailure extends Schema.TaggedErrorClass()("LLM.ToolFailure", {
   message: Schema.String,
+  error: Schema.optional(Schema.Defect),
   metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
 }) {}
diff --git a/packages/llm/src/schema/events.ts b/packages/llm/src/schema/events.ts
index 6a088dc87..63c9b7b7d 100644
--- a/packages/llm/src/schema/events.ts
+++ b/packages/llm/src/schema/events.ts
@@ -171,6 +171,7 @@ export const ToolError = Schema.Struct({
   id: ToolCallID,
   name: Schema.String,
   message: Schema.String,
+  error: Schema.optional(Schema.Defect),
   providerMetadata: Schema.optional(ProviderMetadata),
 }).annotate({ identifier: "LLM.Event.ToolError" })
 export type ToolError = Schema.Schema.Type
diff --git a/packages/llm/src/tool-runtime.ts b/packages/llm/src/tool-runtime.ts
index d83dcc67a..ef527faa2 100644
--- a/packages/llm/src/tool-runtime.ts
+++ b/packages/llm/src/tool-runtime.ts
@@ -112,17 +112,29 @@ export const stream = (options: StreamOptions): Stream.Strea
 
             const dispatched = yield* Effect.forEach(
               state.toolCalls,
-              (call) => dispatch(tools, call).pipe(Effect.map((result) => [call, result] as const)),
+              (call) =>
+                dispatch(tools, call).pipe(Effect.map((result) => [call, result.result, result.error] as const)),
               { concurrency },
             )
-            const resultStream = Stream.fromIterable(dispatched.flatMap(([call, result]) => emitEvents(call, result)))
+            const resultStream = Stream.fromIterable(
+              dispatched.flatMap(([call, result, error]) => emitEvents(call, result, error)),
+            )
 
             if (!options.stopWhen) return resultStream.pipe(Stream.concat(finishStream))
             if (options.stopWhen({ step, request })) return resultStream.pipe(Stream.concat(finishStream))
 
             return resultStream.pipe(
               Stream.concat(
-                loop(followUpRequest(request, state, dispatched), step + 1, totalUsage, totalProviderMetadata),
+                loop(
+                  followUpRequest(
+                    request,
+                    state,
+                    dispatched.map(([call, result]) => [call, result] as const),
+                  ),
+                  step + 1,
+                  totalUsage,
+                  totalProviderMetadata,
+                ),
               ),
             )
           }),
@@ -215,7 +227,7 @@ const addUsage = (left: Usage | undefined, right: Usage | undefined) => {
     | "reasoningTokens"
     | "totalTokens"
   const sum = (key: UsageKey) =>
-    left[key] === undefined && right[key] === undefined ? undefined : Number(left[key] ?? 0) + Number(right[key] ?? 0)
+    left[key] === undefined && right[key] === undefined ? undefined : (left[key] ?? 0) + (right[key] ?? 0)
 
   return new Usage({
     inputTokens: sum("inputTokens"),
@@ -264,16 +276,20 @@ const appendStreamingText = (
   state.assistantContent.push({ type, text, providerMetadata })
 }
 
-const dispatch = (tools: Tools, call: ToolCallPart): Effect.Effect => {
+const dispatch = (tools: Tools, call: ToolCallPart): Effect.Effect<{ result: ToolResultValue; error?: unknown }> => {
   const tool = tools[call.name]
-  if (!tool) return Effect.succeed({ type: "error" as const, value: `Unknown tool: ${call.name}` })
+  if (!tool) return Effect.succeed({ result: { type: "error" as const, value: `Unknown tool: ${call.name}` } })
   if (!tool.execute)
-    return Effect.succeed({ type: "error" as const, value: `Tool has no execute handler: ${call.name}` })
+    return Effect.succeed({ result: { type: "error" as const, value: `Tool has no execute handler: ${call.name}` } })
 
   return decodeAndExecute(tool, call).pipe(
     Effect.catchTag("LLM.ToolFailure", (failure) =>
-      Effect.succeed({ type: "error" as const, value: failure.message } satisfies ToolResultValue),
+      Effect.succeed({
+        result: { type: "error" as const, value: failure.message } satisfies ToolResultValue,
+        error: failure.error,
+      }),
     ),
+    Effect.map((result) => ("result" in result ? result : { result })),
   )
 }
 
@@ -294,10 +310,10 @@ const decodeAndExecute = (tool: AnyTool, call: ToolCallPart): Effect.Effect ({ type: "json", value: encoded })),
   )
 
-const emitEvents = (call: ToolCallPart, result: ToolResultValue): ReadonlyArray =>
+const emitEvents = (call: ToolCallPart, result: ToolResultValue, error: unknown): ReadonlyArray =>
   result.type === "error"
     ? [
-        LLMEvent.toolError({ id: call.id, name: call.name, message: String(result.value) }),
+        LLMEvent.toolError({ id: call.id, name: call.name, message: String(result.value), error }),
         LLMEvent.toolResult({ id: call.id, name: call.name, result }),
       ]
     : [LLMEvent.toolResult({ id: call.id, name: call.name, result })]
diff --git a/packages/llm/test/tool-runtime.test.ts b/packages/llm/test/tool-runtime.test.ts
index 573021c4c..81389a466 100644
--- a/packages/llm/test/tool-runtime.test.ts
+++ b/packages/llm/test/tool-runtime.test.ts
@@ -25,6 +25,7 @@ const baseRequest = LLM.request({
   model,
   prompt: "Use the tool.",
 })
+const weatherFailureCause = new Error("weather lookup denied")
 
 const get_weather = tool({
   description: "Get current weather for a city.",
@@ -32,7 +33,8 @@ const get_weather = tool({
   success: Schema.Struct({ temperature: Schema.Number, condition: Schema.String }),
   execute: ({ city }) =>
     Effect.gen(function* () {
-      if (city === "FAIL") return yield* new ToolFailure({ message: `Weather lookup failed for ${city}` })
+      if (city === "FAIL")
+        return yield* new ToolFailure({ message: `Weather lookup failed for ${city}`, error: weatherFailureCause })
       return { temperature: 22, condition: "sunny" }
     }),
 })
@@ -85,23 +87,27 @@ describe("LLMClient tools", () => {
         tools: { get_weather },
       }).pipe(Stream.runCollect, Effect.provide(layer))
 
-      const second = bodies[1] as {
-        readonly messages?: ReadonlyArray>
-        readonly tools?: ReadonlyArray
-        readonly tool_choice?: unknown
-        readonly max_tokens?: unknown
-      }
-
-      expect(second.max_tokens).toBe(50)
-      expect(second.tool_choice).toBe("auto")
-      expect(second.tools).toHaveLength(1)
-      expect(second.messages?.map((message) => message.role)).toEqual(["user", "assistant", "tool"])
-      expect(second.messages?.[1]).toMatchObject({
+      const second = bodies[1]
+      if (!second || typeof second !== "object") throw new Error("Expected second request body")
+      const messages = Reflect.get(second, "messages")
+      const tools = Reflect.get(second, "tools")
+
+      expect(Reflect.get(second, "max_tokens")).toBe(50)
+      expect(Reflect.get(second, "tool_choice")).toBe("auto")
+      expect(tools).toHaveLength(1)
+      expect(
+        Array.isArray(messages)
+          ? messages.map((message) =>
+              message && typeof message === "object" ? Reflect.get(message, "role") : undefined,
+            )
+          : undefined,
+      ).toEqual(["user", "assistant", "tool"])
+      expect(Array.isArray(messages) ? messages[1] : undefined).toMatchObject({
         role: "assistant",
         content: null,
         tool_calls: [{ id: "call_1", type: "function", function: { name: "get_weather" } }],
       })
-      expect(second.messages?.[2]).toMatchObject({
+      expect(Array.isArray(messages) ? messages[2] : undefined).toMatchObject({
         role: "tool",
         tool_call_id: "call_1",
         content: '{"temperature":22,"condition":"sunny"}',
@@ -327,6 +333,7 @@ describe("LLMClient tools", () => {
       const toolError = events.find(LLMEvent.is.toolError)
       expect(toolError).toMatchObject({ type: "tool-error", id: "call_1", name: "get_weather" })
       expect(toolError?.message).toBe("Weather lookup failed for FAIL")
+      expect(toolError?.error).toBe(weatherFailureCause)
     }),
   )
 

From 5e41dbbcbf7fb437a877806ecbb48ebcf094d72e Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 20:43:46 -0400
Subject: [PATCH 309/378] test(effect): use Effect sleep in instance state
 tests (#27404)

---
 .../opencode/test/effect/instance-state.test.ts    | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/packages/opencode/test/effect/instance-state.test.ts b/packages/opencode/test/effect/instance-state.test.ts
index f5e693388..b928f16bc 100644
--- a/packages/opencode/test/effect/instance-state.test.ts
+++ b/packages/opencode/test/effect/instance-state.test.ts
@@ -159,7 +159,7 @@ it.live("InstanceState preserves directory across async boundaries", () =>
 
           return Test.of({
             get: Effect.fn("Test.get")(function* () {
-              yield* Effect.promise(() => Bun.sleep(1))
+              yield* Effect.sleep(Duration.millis(1))
               yield* Effect.sleep(Duration.millis(1))
               for (let i = 0; i < 100; i++) {
                 yield* Effect.yieldNow
@@ -168,7 +168,7 @@ it.live("InstanceState preserves directory across async boundaries", () =>
                 yield* Effect.promise(() => Promise.resolve())
               }
               yield* Effect.sleep(Duration.millis(2))
-              yield* Effect.promise(() => Bun.sleep(1))
+              yield* Effect.sleep(Duration.millis(1))
               return yield* InstanceState.get(state)
             }),
           })
@@ -212,7 +212,7 @@ it.live("InstanceState survives high-contention concurrent access", () =>
           return Test.of({
             get: Effect.fn("Test.get")(function* () {
               for (let i = 0; i < 10; i++) {
-                yield* Effect.promise(() => Bun.sleep(Math.random() * 3))
+                yield* Effect.sleep(Duration.millis(Math.random() * 3))
                 yield* Effect.yieldNow
                 yield* Effect.promise(() => Promise.resolve())
               }
@@ -248,8 +248,8 @@ it.live("InstanceState correct after interleaved init and dispose", () =>
         Test,
         Effect.gen(function* () {
           const state = yield* InstanceState.make((ctx) =>
-            Effect.promise(async () => {
-              await Bun.sleep(5)
+            Effect.gen(function* () {
+              yield* Effect.sleep(Duration.millis(5))
               return ctx.directory
             }),
           )
@@ -305,9 +305,9 @@ it.live("InstanceState dedupes concurrent lookups", () =>
     const dir = yield* tmpdirScoped()
     let n = 0
     const state = yield* InstanceState.make(() =>
-      Effect.promise(async () => {
+      Effect.gen(function* () {
         n += 1
-        await Bun.sleep(10)
+        yield* Effect.sleep(Duration.millis(10))
         return { n }
       }),
     )

From 9818c9e8d04aa9ff0a0257bef9ee44ded0590673 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 20:44:34 -0400
Subject: [PATCH 310/378] fix(provider): make small model fallback optional
 (#27405)

---
 packages/opencode/src/provider/provider.ts    | 12 ++++++-----
 .../opencode/test/provider/provider.test.ts   | 21 +++++++++++++++++++
 2 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 46c1c56d6..d2c6eafd4 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -1707,7 +1707,9 @@ const layer = Layer.effect(
 
       if (cfg.small_model) {
         const parsed = parseModel(cfg.small_model)
-        return yield* getModel(parsed.providerID, parsed.modelID).pipe(Effect.orDie)
+        return yield* getModel(parsed.providerID, parsed.modelID).pipe(
+          Effect.catchTag("ProviderModelNotFoundError", () => Effect.succeed(undefined)),
+        )
       }
 
       const s = yield* InstanceState.get(state)
@@ -1735,22 +1737,22 @@ const layer = Layer.effect(
           const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
 
           const globalMatch = candidates.find((m) => m.startsWith("global."))
-          if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch)).pipe(Effect.orDie)
+          if (globalMatch) return provider.models[globalMatch]
 
           const region = provider.options?.region
           if (region) {
             const regionPrefix = region.split("-")[0]
             if (regionPrefix === "us" || regionPrefix === "eu") {
               const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
-              if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch)).pipe(Effect.orDie)
+              if (regionalMatch) return provider.models[regionalMatch]
             }
           }
 
           const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
-          if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed)).pipe(Effect.orDie)
+          if (unprefixed) return provider.models[unprefixed]
         } else {
           for (const model of Object.keys(provider.models)) {
-            if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model)).pipe(Effect.orDie)
+            if (model.includes(item)) return provider.models[model]
           }
         }
       }
diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts
index aafc19255..da708a59d 100644
--- a/packages/opencode/test/provider/provider.test.ts
+++ b/packages/opencode/test/provider/provider.test.ts
@@ -1025,6 +1025,27 @@ test("getSmallModel respects config small_model override", async () => {
   })
 })
 
+test("getSmallModel ignores invalid config small_model", async () => {
+  await using tmp = await tmpdir({
+    init: async (dir) => {
+      await Bun.write(
+        path.join(dir, "opencode.json"),
+        JSON.stringify({
+          $schema: "https://opencode.ai/config.json",
+          small_model: "anthropic/not-a-real-model",
+        }),
+      )
+    },
+  })
+  await WithInstance.provide({
+    directory: tmp.path,
+    fn: async () => {
+      set("ANTHROPIC_API_KEY", "test-api-key")
+      expect(await getSmallModel(ProviderID.anthropic)).toBeUndefined()
+    },
+  })
+})
+
 test("provider.sort prioritizes preferred models", () => {
   const models = [
     { id: "random-model", name: "Random" },

From 16c457e71233b2eca6a73aa292ec1ed225d87af7 Mon Sep 17 00:00:00 2001
From: Dax 
Date: Wed, 13 May 2026 20:58:24 -0400
Subject: [PATCH 311/378] refactor(core): move models.dev into core (#27347)

---
 packages/core/src/catalog.ts                  |  2 +
 packages/core/src/instance-layer.ts           | 12 ++++
 packages/core/src/instance.ts                 | 10 +++
 packages/core/src/models-snapshot.d.ts        |  2 +
 packages/core/src/models-snapshot.js          |  3 +
 .../src/provider => core/src}/models.ts       | 36 ++++++----
 packages/core/src/plugin/boot.ts              | 66 +++++++++++++++++++
 .../src/v2 => core/src}/plugin/models-dev.ts  | 10 +--
 packages/core/test/{v2 => }/catalog.test.ts   |  6 +-
 .../provider => core/test}/models.test.ts     | 14 ++--
 .../plugin/fixtures/provider-factory.ts       |  0
 .../{v2 => }/plugin/provider-alibaba.test.ts  |  0
 .../plugin/provider-amazon-bedrock.test.ts    |  0
 .../plugin/provider-anthropic.test.ts         |  0
 .../provider-azure-cognitive-services.test.ts |  0
 .../{v2 => }/plugin/provider-azure.test.ts    |  2 +-
 .../{v2 => }/plugin/provider-cerebras.test.ts |  0
 .../provider-cloudflare-ai-gateway.test.ts    |  0
 .../provider-cloudflare-workers-ai.test.ts    |  2 +-
 .../{v2 => }/plugin/provider-cohere.test.ts   |  0
 .../plugin/provider-deepinfra.test.ts         |  2 +-
 .../{v2 => }/plugin/provider-dynamic.test.ts  |  2 +-
 .../{v2 => }/plugin/provider-gateway.test.ts  |  0
 .../plugin/provider-github-copilot.test.ts    |  2 +-
 .../{v2 => }/plugin/provider-gitlab.test.ts   |  2 +-
 .../provider-google-vertex-anthropic.test.ts  |  0
 .../plugin/provider-google-vertex.test.ts     |  0
 .../{v2 => }/plugin/provider-google.test.ts   |  2 +-
 .../{v2 => }/plugin/provider-groq.test.ts     |  2 +-
 .../test/{v2 => }/plugin/provider-helper.ts   |  2 +-
 .../{v2 => }/plugin/provider-kilo.test.ts     |  0
 .../plugin/provider-llmgateway.test.ts        |  0
 .../{v2 => }/plugin/provider-mistral.test.ts  |  0
 .../{v2 => }/plugin/provider-nvidia.test.ts   |  0
 .../plugin/provider-openai-compatible.test.ts |  0
 .../{v2 => }/plugin/provider-openai.test.ts   |  0
 .../{v2 => }/plugin/provider-opencode.test.ts |  6 +-
 .../plugin/provider-openrouter.test.ts        |  0
 .../plugin/provider-perplexity.test.ts        |  0
 .../plugin/provider-sap-ai-core.test.ts       |  0
 .../plugin/provider-togetherai.test.ts        |  0
 .../{v2 => }/plugin/provider-venice.test.ts   |  0
 .../{v2 => }/plugin/provider-vercel.test.ts   |  0
 .../test/{v2 => }/plugin/provider-xai.test.ts |  2 +-
 .../{v2 => }/plugin/provider-zenmux.test.ts   |  0
 packages/opencode/script/generate.ts          |  4 +-
 packages/opencode/src/cli/cmd/debug/v2.ts     | 26 +++++---
 packages/opencode/src/cli/cmd/github.ts       |  2 +-
 packages/opencode/src/cli/cmd/models.ts       |  2 +-
 packages/opencode/src/cli/cmd/providers.ts    |  2 +-
 packages/opencode/src/effect/app-runtime.ts   |  2 +-
 .../opencode/src/provider/model-status.ts     |  3 +-
 packages/opencode/src/provider/provider.ts    |  2 +-
 packages/opencode/src/provider/transform.ts   |  2 +-
 .../instance/httpapi/groups/v2/instance.ts    | 59 +++++++++++++++++
 .../instance/httpapi/groups/v2/model.ts       |  5 +-
 .../instance/httpapi/groups/v2/provider.ts    |  8 ++-
 .../instance/httpapi/handlers/provider.ts     |  2 +-
 .../routes/instance/httpapi/handlers/v2.ts    |  4 +-
 .../instance/httpapi/handlers/v2/model.ts     | 13 +++-
 .../instance/httpapi/handlers/v2/provider.ts  | 16 ++++-
 .../server/routes/instance/httpapi/server.ts  |  2 +-
 packages/opencode/src/v2/plugin-boot.ts       | 50 --------------
 .../test/provider/model-status.test.ts        |  2 +-
 .../opencode/test/provider/provider.test.ts   |  2 +-
 packages/opencode/test/session/llm.test.ts    |  2 +-
 packages/sdk/js/src/v2/gen/sdk.gen.ts         | 40 ++++++++++-
 packages/sdk/js/src/v2/gen/types.gen.ts       | 61 ++++++++++-------
 68 files changed, 345 insertions(+), 153 deletions(-)
 create mode 100644 packages/core/src/instance-layer.ts
 create mode 100644 packages/core/src/instance.ts
 create mode 100644 packages/core/src/models-snapshot.d.ts
 create mode 100644 packages/core/src/models-snapshot.js
 rename packages/{opencode/src/provider => core/src}/models.ts (89%)
 create mode 100644 packages/core/src/plugin/boot.ts
 rename packages/{opencode/src/v2 => core/src}/plugin/models-dev.ts (92%)
 rename packages/core/test/{v2 => }/catalog.test.ts (96%)
 rename packages/{opencode/test/provider => core/test}/models.test.ts (94%)
 rename packages/core/test/{v2 => }/plugin/fixtures/provider-factory.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-alibaba.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-amazon-bedrock.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-anthropic.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-azure-cognitive-services.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-azure.test.ts (99%)
 rename packages/core/test/{v2 => }/plugin/provider-cerebras.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-cloudflare-ai-gateway.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-cloudflare-workers-ai.test.ts (99%)
 rename packages/core/test/{v2 => }/plugin/provider-cohere.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-deepinfra.test.ts (98%)
 rename packages/core/test/{v2 => }/plugin/provider-dynamic.test.ts (99%)
 rename packages/core/test/{v2 => }/plugin/provider-gateway.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-gitlab.test.ts (99%)
 rename packages/core/test/{v2 => }/plugin/provider-google-vertex-anthropic.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-google-vertex.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-google.test.ts (98%)
 rename packages/core/test/{v2 => }/plugin/provider-groq.test.ts (98%)
 rename packages/core/test/{v2 => }/plugin/provider-helper.ts (98%)
 rename packages/core/test/{v2 => }/plugin/provider-kilo.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-llmgateway.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-mistral.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-nvidia.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-openai-compatible.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-openai.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-opencode.test.ts (96%)
 rename packages/core/test/{v2 => }/plugin/provider-openrouter.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-perplexity.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-sap-ai-core.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-togetherai.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-venice.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-vercel.test.ts (100%)
 rename packages/core/test/{v2 => }/plugin/provider-xai.test.ts (98%)
 rename packages/core/test/{v2 => }/plugin/provider-zenmux.test.ts (100%)
 create mode 100644 packages/opencode/src/server/routes/instance/httpapi/groups/v2/instance.ts
 delete mode 100644 packages/opencode/src/v2/plugin-boot.ts

diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts
index 3aa591542..70cdba785 100644
--- a/packages/core/src/catalog.ts
+++ b/packages/core/src/catalog.ts
@@ -5,6 +5,7 @@ import { produce, type Draft } from "immer"
 import { ModelV2 } from "./model"
 import { PluginV2 } from "./plugin"
 import { ProviderV2 } from "./provider"
+import { Instance } from "./instance"
 
 type ProviderRecord = {
   provider: ProviderV2.Info
@@ -56,6 +57,7 @@ export class Service extends Context.Service()("@opencode/v2
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {
+    yield* Instance.Service
     let records = HashMap.empty()
     let defaultModel: { providerID: ProviderV2.ID; modelID: ModelV2.ID } | undefined
     const plugin = yield* PluginV2.Service
diff --git a/packages/core/src/instance-layer.ts b/packages/core/src/instance-layer.ts
new file mode 100644
index 000000000..20e6ac1ac
--- /dev/null
+++ b/packages/core/src/instance-layer.ts
@@ -0,0 +1,12 @@
+import { Layer, LayerMap } from "effect"
+import { Instance } from "./instance"
+import { Catalog } from "./catalog"
+import { PluginBoot } from "./plugin/boot"
+
+export class InstanceServiceMap extends LayerMap.Service()("@opencode/example/InstanceServiceMap", {
+  lookup: (ref: Instance.Ref) => {
+    const instance = Layer.succeed(Instance.Service, Instance.Service.of(ref))
+    return Layer.mergeAll(Catalog.defaultLayer, PluginBoot.defaultLayer).pipe(Layer.provide(instance))
+  },
+  idleTimeToLive: "5 minutes",
+}) {}
diff --git a/packages/core/src/instance.ts b/packages/core/src/instance.ts
new file mode 100644
index 000000000..af6de951b
--- /dev/null
+++ b/packages/core/src/instance.ts
@@ -0,0 +1,10 @@
+import { Context } from "effect"
+
+export * as Instance from "./instance"
+
+export type Ref = {
+  readonly directory: string
+  readonly workspaceID?: string
+}
+
+export class Service extends Context.Service()("@opencode/Instance") {}
diff --git a/packages/core/src/models-snapshot.d.ts b/packages/core/src/models-snapshot.d.ts
new file mode 100644
index 000000000..839eba6b7
--- /dev/null
+++ b/packages/core/src/models-snapshot.d.ts
@@ -0,0 +1,2 @@
+// Auto-generated by build.ts - do not edit
+export declare const snapshot: Record
diff --git a/packages/core/src/models-snapshot.js b/packages/core/src/models-snapshot.js
new file mode 100644
index 000000000..817cf7509
--- /dev/null
+++ b/packages/core/src/models-snapshot.js
@@ -0,0 +1,3 @@
+// @ts-nocheck
+// Auto-generated by build.ts - do not edit
+export const snapshot = {"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":{"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}},"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}},"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}},"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}},"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}},"grok-4.20-multi-agent-beta-0309":{"id":"grok-4.20-multi-agent-beta-0309","name":"grok-4.20-multi-agent-beta-0309","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":2000000,"output":30000}},"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}},"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-16","last_updated":"2025-10-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":5},"limit":{"context":200000,"output":64000}},"claude-opus-4-5-20251101":{"id":"claude-opus-4-5-20251101","name":"claude-opus-4-5-20251101","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-03-31","release_date":"2025-11-25","last_updated":"2025-11-25","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25},"limit":{"context":200000,"output":64000}},"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}},"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}},"glm-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video","audio","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.72,"output":3.2},"limit":{"context":200000,"output":131072}},"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.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.286,"output":1.142},"limit":{"context":204800,"output":131072}},"claude-3-5-haiku-20241022":{"id":"claude-3-5-haiku-20241022","name":"claude-3-5-haiku-20241022","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},"limit":{"context":200000,"output":8192}},"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}},"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}},"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-12","last_updated":"2026-02-12","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":2.6},"limit":{"context":204800,"output":131072}},"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}},"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}},"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-31","release_date":"2026-02-18","last_updated":"2026-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":1000000,"output":64000}},"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}},"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-20","last_updated":"2026-01-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.0715,"output":0.429},"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}},"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}},"grok-4.20-beta-0309-non-reasoning":{"id":"grok-4.20-beta-0309-non-reasoning","name":"grok-4.20-beta-0309-non-reasoning","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":2000000,"output":30000}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"claude-opus-4-7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2026-01-31","release_date":"2026-04-17","last_updated":"2026-04-17","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}},"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-08","last_updated":"2025-08-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":2},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"MiniMax-M2.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2},"limit":{"context":204800,"output":131072}},"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}},"claude-sonnet-4-5-20250929":{"id":"claude-sonnet-4-5-20250929","name":"claude-sonnet-4-5-20250929","family":"claude-sonnet","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-07-31","release_date":"2025-09-30","last_updated":"2025-09-30","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":200000,"output":64000}},"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}},"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}},"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}},"claude-3-5-haiku-latest":{"id":"claude-3-5-haiku-latest","name":"claude-3-5-haiku-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},"limit":{"context":200000,"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}},"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-12","last_updated":"2025-12-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.75,"output":14},"limit":{"context":400000,"input":272000,"output":128000}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"gpt-5.4-mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5},"limit":{"context":400000,"input":272000,"output":128000}},"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-5.1":{"id":"glm-5.1","name":"glm-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-10","last_updated":"2026-04-10","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.86,"output":3.5},"limit":{"context":200000,"output":131072}},"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}},"gpt-5.4-nano":{"id":"gpt-5.4-nano","name":"gpt-5.4-nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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-29","last_updated":"2025-07-29","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.286,"output":1.142},"limit":{"context":131072,"output":98304}},"gpt-5.4-mini-2026-03-17":{"id":"gpt-5.4-mini-2026-03-17","name":"gpt-5.4-mini-2026-03-17","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5},"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":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}},"gpt-5.2-chat-latest":{"id":"gpt-5.2-chat-latest","name":"gpt-5.2-chat-latest","family":"gpt-codex","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","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}},"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}},"gemini-3.1-flash-image-preview":{"id":"gemini-3.1-flash-image-preview","name":"gemini-3.1-flash-image-preview","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2025-01","release_date":"2026-02-27","last_updated":"2026-02-27","modalities":{"input":["text","image","pdf"],"output":["text","image"]},"open_weights":false,"cost":{"input":0.5,"output":60},"limit":{"context":131072,"output":32768}},"MiniMax-M2.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":4.8},"limit":{"context":204800,"output":131072}},"glm-4.5-x":{"id":"glm-4.5-x","name":"glm-4.5-x","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2025-07-29","last_updated":"2025-07-29","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.143,"output":2.29},"limit":{"context":128000,"output":16384}},"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}},"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"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":10},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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}},"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}},"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}},"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-29","last_updated":"2025-07-29","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1143,"output":0.286},"limit":{"context":131072,"output":98304}},"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,"cache_read":0,"cache_write":0,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"output":128000}},"glm-5-turbo":{"id":"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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.72,"output":3.2},"limit":{"context":200000,"output":131072}},"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}},"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-25","last_updated":"2025-11-25","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25},"limit":{"context":200000,"output":64000}},"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-12","last_updated":"2025-08-12","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.29,"output":0.86},"limit":{"context":64000,"output":16384}},"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.286,"output":1.142},"limit":{"context":204800,"output":131072}},"claude-opus-4-6-thinking":{"id":"claude-opus-4-6-thinking","name":"claude-opus-4-6-thinking","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-02-06","last_updated":"2026-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25},"limit":{"context":1000000,"output":128000}},"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}},"claude-sonnet-4-6-thinking":{"id":"claude-sonnet-4-6-thinking","name":"claude-sonnet-4-6-thinking","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08","release_date":"2026-02-18","last_updated":"2026-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":1000000,"output":64000}},"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.145,"output":0.43},"limit":{"context":128000,"output":32768}},"claude-opus-4-1-20250805":{"id":"claude-opus-4-1-20250805","name":"claude-opus-4-1-20250805","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},"limit":{"context":200000,"output":32000}},"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,"cache_write":0,"context_over_200k":{"input":5,"output":22.5}},"limit":{"context":1050000,"input":922000,"output":128000}},"gpt-5.1-chat-latest":{"id":"gpt-5.1-chat-latest","name":"gpt-5.1-chat-latest","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"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":10},"limit":{"context":128000,"output":16384}},"claude-haiku-4-5-20251001":{"id":"claude-haiku-4-5-20251001","name":"claude-haiku-4-5-20251001","family":"claude-haiku","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-02-28","release_date":"2025-10-16","last_updated":"2025-10-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":5},"limit":{"context":200000,"output":64000}},"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}},"gpt-5.4-nano-2026-03-17":{"id":"gpt-5.4-nano-2026-03-17","name":"gpt-5.4-nano-2026-03-17","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25},"limit":{"context":400000,"input":272000,"output":128000}},"claude-sonnet-4-20250514":{"id":"claude-sonnet-4-20250514","name":"claude-sonnet-4-20250514","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},"limit":{"context":200000,"output":64000}},"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}},"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-06","last_updated":"2026-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25},"limit":{"context":1000000,"output":128000}},"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}},"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},"limit":{"context":1047576,"output":32768}},"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}},"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-08","last_updated":"2025-10-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":120},"limit":{"context":400000,"input":272000,"output":272000}},"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-05-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10},"limit":{"context":128000,"output":16384}},"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-30","last_updated":"2025-09-30","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"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-08","last_updated":"2025-08-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":10},"limit":{"context":400000,"input":272000,"output":128000}},"grok-4.20-beta-0309-reasoning":{"id":"grok-4.20-beta-0309-reasoning","name":"grok-4.20-beta-0309-reasoning","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":2000000,"output":30000}},"claude-opus-4-20250514":{"id":"claude-opus-4-20250514","name":"claude-opus-4-20250514","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},"limit":{"context":200000,"output":32000}},"glm-for-coding":{"id":"glm-for-coding","name":"glm-for-coding","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-09-30","last_updated":"2025-09-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.086,"output":0.343},"limit":{"context":200000,"output":131072}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":200000,"output":64000}},"glm-4.5-airx":{"id":"glm-4.5-airx","name":"glm-4.5-airx","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2025-07-29","last_updated":"2025-07-29","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.572,"output":1.714},"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,"structured_output":true,"temperature":true,"knowledge":"2024-04","release_date":"2025-04-14","last_updated":"2025-04-14","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8},"limit":{"context":1047576,"output":32768}},"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}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":1.6},"limit":{"context":1047576,"output":32768}},"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}},"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}},"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}}}},"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":{"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.5-122b-a10b":{"id":"qwen3.5-122b-a10b","name":"Qwen3.5 122B-A10B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-23","last_updated":"2026-02-23","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":3.2},"limit":{"context":262144,"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":1,"output":5},"limit":{"context":1048576,"output":65536}},"qwen3.6-27b":{"id":"qwen3.6-27b","name":"Qwen3.6 27B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":65536}},"qwen3.5-27b":{"id":"qwen3.5-27b","name":"Qwen3.5 27B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-23","last_updated":"2026-02-23","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.4},"limit":{"context":262144,"output":65536}},"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}},"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-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.5-397b-a17b":{"id":"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-15","last_updated":"2026-02-15","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":65536}},"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}},"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}},"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}},"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}},"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-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}},"qwen3.6-35b-a3b":{"id":"qwen3.6-35b-a3b","name":"Qwen3.6 35B-A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.248,"output":1.485},"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-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-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}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.276,"output":1.651,"cache_read":0.028,"cache_write":0.344},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"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-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}},"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}},"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}},"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}},"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-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-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-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-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}},"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}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.3,"output":7.8,"cache_read":0.13,"cache_write":1.625},"limit":{"context":262144,"output":65536}},"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}},"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}},"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}},"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}},"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.5-35b-a3b":{"id":"qwen3.5-35b-a3b","name":"Qwen3.5 35B-A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-23","last_updated":"2026-02-23","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2},"limit":{"context":262144,"output":65536}}}},"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":{"qwen3-embedding-8b":{"id":"qwen3-embedding-8b","name":"Qwen3 Embedding 8B","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-25-11","last_updated":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.1,"output":0},"limit":{"context":32768,"output":4096}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.75,"output":2.25},"limit":{"context":260000,"output":16384}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.9,"output":0.9},"limit":{"context":100000,"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-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":256000,"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-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2},"limit":{"context":256000,"output":16384}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.9,"output":0.9},"limit":{"context":32000,"output":8196}},"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":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":128000,"output":4096}},"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":"2026-03-17","modalities":{"input":["audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.003,"output":0},"limit":{"context":0,"output":8192}},"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":"2026-03-17","modalities":{"input":["text","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.35},"limit":{"context":32000,"output":16384}},"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":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":0.5},"limit":{"context":40000,"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.1,"output":0},"limit":{"context":8191,"output":3072}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.8},"limit":{"context":128000,"output":32768}},"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":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.35},"limit":{"context":128000,"output":32768}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":128000,"output":32768}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":128000,"output":8192}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":128000,"output":16384}}}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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.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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"qwen-3.6-plus":{"id":"qwen-3.6-plus","name":"Qwen 3.6 Plus","family":"qwen3.6","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.45,"output":2.7},"limit":{"context":991800,"output":65536}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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-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-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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen3.6","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"release_date":"2026-04-20","last_updated":"2026-04-21","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.3,"output":7.8},"limit":{"context":245800,"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"alibaba/qwen3.6-flash":{"id":"alibaba/qwen3.6-flash","name":"Qwen3.6 Flash","family":"qwen3.6","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.19,"output":1.16},"limit":{"context":991800,"output":65536}},"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}},"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}},"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}},"LLM360/K2-Think":{"id":"LLM360/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}},"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/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/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/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/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/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-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/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/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/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/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/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/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/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}},"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.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/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/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/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}},"CrucibleLab/L3.3-70B-Loki-V2.0":{"id":"CrucibleLab/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}},"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}},"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}},"Doctor-Shotgun/MS3.2-24B-Magnum-Diamond":{"id":"Doctor-Shotgun/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}},"NeverSleep/Llama-3-Lumimaid-70B-v0.1":{"id":"NeverSleep/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/Lumimaid-v0.2-70B":{"id":"NeverSleep/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}},"Steelskull/L3.3-Cu-Mai-R1-70b":{"id":"Steelskull/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/L3.3-Nevoria-R1-70b":{"id":"Steelskull/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/L3.3-MS-Evayale-70B":{"id":"Steelskull/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}},"Steelskull/L3.3-Electra-R1-70b":{"id":"Steelskull/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/L3.3-MS-Nevoria-70b":{"id":"Steelskull/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/L3.3-MS-Evalebis-70b":{"id":"Steelskull/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}},"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}},"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}},"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}},"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}},"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}},"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}},"Salesforce/Llama-xLAM-2-70b-fc-r":{"id":"Salesforce/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}},"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/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: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}},"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/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}},"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/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}},"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}},"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/Llama-3.05-NT-Storybreaker-Ministral-70B":{"id":"Envoid/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}},"Envoid/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B":{"id":"Envoid/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}},"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}},"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}},"ReadyArt/MS3.2-The-Omega-Directive-24B-Unslop-v2.0":{"id":"ReadyArt/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}},"ReadyArt/The-Omega-Abomination-L-70B-v1.0":{"id":"ReadyArt/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}},"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}},"MarinaraSpaghetti/NemoMix-Unleashed-12B":{"id":"MarinaraSpaghetti/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}},"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}},"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}},"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}},"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":{"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-5.1":{"id":"zai-org/glm-5.1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.55},"limit":{"context":200000,"input":200000,"output":131072}},"zai-org/glm-5.1:thinking":{"id":"zai-org/glm-5.1:thinking","name":"GLM 5.1 Thinking","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.55},"limit":{"context":200000,"input":200000,"output":131072}},"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}},"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}},"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}},"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}},"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}},"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-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-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/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/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/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/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/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/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}},"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-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/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-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-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-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/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/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}},"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}},"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-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-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.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.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-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}},"GalrionSoftworks/MN-LooseCannon-12B-v1":{"id":"GalrionSoftworks/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}},"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}},"Gryphe/MythoMax-L2-13b":{"id":"Gryphe/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}},"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-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}},"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-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":{"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.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}},"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}},"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}},"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}},"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}},"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":true,"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}},"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}},"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/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-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/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}},"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.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/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/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/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-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}},"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-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}},"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: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}},"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-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-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-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}},"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-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/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/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/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/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/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.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/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/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/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/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-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/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/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-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.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/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-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/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/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-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/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-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.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-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}},"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/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/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-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-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-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-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/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-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/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/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-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-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-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/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/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/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}},"VongolaChouko/Starcannon-Unleashed-12B-v1.0":{"id":"VongolaChouko/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}},"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-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}},"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-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}},"Sao10K/L3.3-70B-Euryale-v2.3":{"id":"Sao10K/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/L3.1-70B-Euryale-v2.2":{"id":"Sao10K/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/L3.1-70B-Hanami-x1":{"id":"Sao10K/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/L3-8B-Stheno-v3.2":{"id":"Sao10K/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}},"LatitudeGames/Wayfarer-Large-70B-Llama-3.3":{"id":"LatitudeGames/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}},"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":{"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}},"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: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}},"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}},"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}},"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}},"Infermatic/MN-12B-Inferor-v0.0":{"id":"Infermatic/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}},"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}},"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}},"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-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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2},"limit":{"context":204800,"input":204800,"output":131072}},"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}},"qwen/Qwen3.6-35B-A3B:thinking":{"id":"qwen/Qwen3.6-35B-A3B:thinking","name":"Qwen3.6 35B A3B Thinking","family":"qwen3.6","attachment":false,"reasoning":true,"tool_call":false,"structured_output":false,"release_date":"2026-04-19","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.29,"output":1.74},"limit":{"context":262144,"output":16384}},"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}},"qwen/Qwen3.6-35B-A3B":{"id":"qwen/Qwen3.6-35B-A3B","name":"Qwen3.6 35B A3B","family":"qwen3.6","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"release_date":"2026-04-17","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.29,"output":1.74},"limit":{"context":262144,"output":16384}},"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}},"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-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-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}},"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-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}},"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-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-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}},"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-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-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}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":false,"tool_call":true,"structured_output":false,"release_date":"2026-04-16","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.53,"output":2.73},"limit":{"context":256000,"output":65536}},"moonshotai/kimi-k2.6:thinking":{"id":"moonshotai/kimi-k2.6:thinking","name":"Kimi K2.6 Thinking","family":"kimi-thinking","attachment":true,"reasoning":true,"tool_call":true,"structured_output":false,"release_date":"2026-04-16","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.53,"output":2.73},"limit":{"context":256000,"output":65536}},"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-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.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}},"Tongyi-Zhiwen/QwenLong-L1-32B":{"id":"Tongyi-Zhiwen/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}},"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}},"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}},"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-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}},"Alibaba-NLP/Tongyi-DeepResearch-30B-A3B":{"id":"Alibaba-NLP/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}},"MiniMaxAI/MiniMax-M1-80k":{"id":"MiniMaxAI/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}},"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":{"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}},"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,"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":2.992,"output":14.993999999999998},"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: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-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,"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":2.992,"output":14.993999999999998},"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}},"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}},"EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.0":{"id":"EVA-UNIT-01/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/EVA-Qwen2.5-72B-v0.2":{"id":"EVA-UNIT-01/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/EVA-LLaMA-3.33-70B-v0.1":{"id":"EVA-UNIT-01/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/EVA-Qwen2.5-32B-v0.2":{"id":"EVA-UNIT-01/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}},"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/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/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/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/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}},"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}},"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":{"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-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}},"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}},"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}},"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}}}},"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-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}},"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}},"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}},"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}},"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-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},"limit":{"context":200000,"output":64000}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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.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}},"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}},"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}},"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}},"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-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}},"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.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}},"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-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}},"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}},"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}},"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}},"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}},"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-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-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}},"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}},"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-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}},"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}},"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}},"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},"limit":{"context":200000,"output":128000}},"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-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}},"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}},"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}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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/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/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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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":{"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}},"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}},"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}},"openai/gpt-5.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"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}},"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.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.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}},"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-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-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}},"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}},"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-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-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},"limit":{"context":200000,"output":64000}},"anthropic/claude-opus-4-7":{"id":"anthropic/claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25,"cache_read":0.5},"limit":{"context":1000000,"output":128000}},"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-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-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},"limit":{"context":200000,"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},"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":{"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}},"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-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-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-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/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.6-35B-A3B":{"id":"Qwen/Qwen3.6-35B-A3B","name":"Qwen/Qwen3.6-35B-A3B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.23,"output":1.86},"limit":{"context":262144,"output":65536}},"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/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-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-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-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/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/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-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/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-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-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/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-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-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-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-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-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-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-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-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-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-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-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/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-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/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-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-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-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-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/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}},"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}},"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.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-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}},"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/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}},"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}},"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}},"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.1":{"id":"Pro/zai-org/GLM-5.1","name":"Pro/zai-org/GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_write":0},"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/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-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.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-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}},"Pro/moonshotai/Kimi-K2.6":{"id":"Pro/moonshotai/Kimi-K2.6","name":"Pro/moonshotai/Kimi-K2.6","family":"kimi","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262000,"output":262000}},"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,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"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.25},"limit":{"context":262000,"output":262000}},"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}},"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}},"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}},"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.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-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":{"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-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-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-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}},"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}},"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-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-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}},"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-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}},"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}}}},"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":{"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}},"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-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}},"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-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}},"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}},"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-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":{"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":{"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.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"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}}}},"perplexity":{"id":"perplexity","env":["PERPLEXITY_API_KEY"],"npm":"@ai-sdk/perplexity","name":"Perplexity","doc":"https://docs.perplexity.ai","models":{"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}},"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":{"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-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}}}},"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-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.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"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.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}}}},"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":{"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-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-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}},"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}},"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-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-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}}}},"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":{"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}},"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}},"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-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-r1":{"id":"deepseek/deepseek-r1","name":"DeepSeek: R1","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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":0.7,"output":2.5},"limit":{"context":64000,"output":16000}},"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-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.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-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-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}},"openrouter/owl-alpha":{"id":"openrouter/owl-alpha","name":"Owl Alpha","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":1048756,"output":262144},"status":"alpha"},"openrouter/pareto-code":{"id":"openrouter/pareto-code","name":"Pareto Code Router","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":200000,"output":200000}},"openrouter/elephant-alpha":{"id":"openrouter/elephant-alpha","name":"Elephant (free)","family":"elephant","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-13","last_updated":"2026-04-13","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"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}},"arcee-ai/trinity-large-thinking":{"id":"arcee-ai/trinity-large-thinking","name":"Trinity Large Thinking","family":"trinity","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.22,"output":0.85},"limit":{"context":262144,"output":80000}},"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}},"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}},"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}},"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-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-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-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-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}},"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}},"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}},"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-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/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/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}},"mistralai/mistral-small-2603":{"id":"mistralai/mistral-small-2603","name":"Mistral Small 4","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":262144,"output":262144}},"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/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-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/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-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}},"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.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}},"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-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-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-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}},"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-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.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-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-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":{"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}},"tencent/hy3-preview":{"id":"tencent/hy3-preview","name":"Hy3 preview","family":"Hy","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.066,"output":0.26,"cache_read":0.029,"cache_write":0.029},"limit":{"context":256000,"output":64000}},"poolside/laguna-m.1:free":{"id":"poolside/laguna-m.1:free","name":"Laguna M.1","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":8192}},"poolside/laguna-xs.2:free":{"id":"poolside/laguna-xs.2:free","name":"Laguna XS.2","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":8192}},"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}},"nvidia/nemotron-3-super-120b-a12b":{"id":"nvidia/nemotron-3-super-120b-a12b","name":"Nemotron 3 Super","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.5},"limit":{"context":262144,"output":262144}},"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free":{"id":"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free","name":"Nemotron 3 Nano Omni (free)","family":"nemotron","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-28","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":256000,"output":65536}},"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: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-3-super-120b-a12b:free":{"id":"nvidia/nemotron-3-super-120b-a12b:free","name":"Nemotron 3 Super (free)","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"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}},"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}},"inception/mercury-edit-2":{"id":"inception/mercury-edit-2","name":"Mercury Edit 2","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":0.75,"cache_read":0.025},"limit":{"context":128000,"output":8192}},"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}},"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-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-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-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.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-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}},"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.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-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.4-mini":{"id":"openai/gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"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/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-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"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-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-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":{"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.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-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-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-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-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":{"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/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-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-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-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.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}},"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.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-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-5.1":{"id":"z-ai/glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202752,"output":131072}},"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.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-5-turbo":{"id":"z-ai/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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.96,"output":3.2,"cache_read":0.192,"cache_write":0},"limit":{"context":202752,"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}},"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-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}},"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}},"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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"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-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.5:free":{"id":"minimax/minimax-m2.5:free","name":"MiniMax M2.5 (free)","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,"output":0},"limit":{"context":204800,"output":131072}},"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-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}},"qwen/qwen3-coder-plus":{"id":"qwen/qwen3-coder-plus","name":"Qwen3 Coder Plus","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2025-09-23","last_updated":"2025-09-23","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.65,"output":3.25,"cache_read":0.13,"cache_write":0.8125},"limit":{"context":1000000,"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,"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/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/qwen-plus":{"id":"qwen/qwen-plus","name":"Qwen: Qwen-Plus","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"structured_output":false,"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.26,"output":0.78,"cache_read":0.052,"cache_write":0.325},"limit":{"context":1000000,"output":32768}},"qwen/qwen3.5-flash-02-23":{"id":"qwen/qwen3.5-flash-02-23","name":"Qwen: Qwen3.5-Flash","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-25","last_updated":"2026-02-25","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.065,"output":0.26},"limit":{"context":1000000,"output":65536}},"qwen/qwen3.6-plus":{"id":"qwen/qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.325,"output":1.95,"cache_read":0.0325,"cache_write":0.40625},"limit":{"context":1000000,"output":65536}},"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,"cache_read":0.156,"cache_write":0.975},"limit":{"context":262144,"output":32768}},"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/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/qwen-3.6-27b":{"id":"qwen/qwen-3.6-27b","name":"Qwen3.6 27B","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.195,"output":1.56},"limit":{"context":262144,"output":81920}},"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-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/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/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,"cache_read":0.039,"cache_write":0.24375},"limit":{"context":128000,"output":66536}},"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/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-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-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/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-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}},"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/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/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/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-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/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.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/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-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-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/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-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/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-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Gemma 4 31B","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.4},"limit":{"context":262144,"output":262144}},"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-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-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}},"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-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-flash","attachment":true,"reasoning":true,"tool_call":false,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"cost":{"input":0.5,"output":3},"limit":{"context":65536,"output":65536}},"google/gemma-4-31b-it:free":{"id":"google/gemma-4-31b-it:free","name":"Gemma 4 31B (free)","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"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/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/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/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/gemma-4-26b-a4b-it":{"id":"google/gemma-4-26b-a4b-it","name":"Gemma 4 26B A4B","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-03","last_updated":"2026-04-03","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.13,"output":0.4},"limit":{"context":262144,"output":262144}},"google/gemma-4-26b-a4b-it:free":{"id":"google/gemma-4-26b-a4b-it:free","name":"Gemma 4 26B A4B (free)","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-03","last_updated":"2026-04-03","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"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}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"structured_output":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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":{"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-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}},"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-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.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-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-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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":{"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.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-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-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,"knowledge":"2025-08-31","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}},"deepseek/deepseek-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1048576,"output":393216}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1048576,"output":393216}},"x-ai/grok-4.3":{"id":"x-ai/grok-4.3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2,"context_over_200k":{"input":2.5,"output":5,"cache_read":0.4}},"limit":{"context":1000000,"output":1000000}},"openai/gpt-5.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000}},"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.5-pro":{"id":"openai/gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"output":128000}},"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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"limit":{"context":1048576,"output":65536}},"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}},"xiaomi/mimo-v2.5-pro":{"id":"xiaomi/mimo-v2.5-pro","name":"Xiaomi: MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-omni":{"id":"xiaomi/mimo-v2-omni","name":"Xiaomi: MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":131072}},"xiaomi/mimo-v2.5":{"id":"xiaomi/mimo-v2.5","name":"Xiaomi: MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-pro":{"id":"xiaomi/mimo-v2-pro","name":"Xiaomi: MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-flash":{"id":"xiaomi/mimo-v2-flash","name":"Xiaomi: MiMo-V2-Flash","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"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":262144,"output":65536}}}},"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/glm-5p1":{"id":"accounts/fireworks/models/glm-5p1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202800,"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/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/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/kimi-k2p6":{"id":"accounts/fireworks/models/kimi-k2p6","name":"Kimi K2.6","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262000,"output":262000}},"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/qwen3p6-plus":{"id":"accounts/fireworks/models/qwen3p6-plus","name":"Qwen 3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-04","last_updated":"2026-04-04","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.1},"limit":{"context":128000,"output":8192}},"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/minimax-m2p7":{"id":"accounts/fireworks/models/minimax-m2p7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-12","last_updated":"2026-04-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/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-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/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/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}},"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-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/routers/kimi-k2p5-turbo":{"id":"accounts/fireworks/routers/kimi-k2p5-turbo","name":"Kimi K2.5 Turbo","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"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":256000,"output":256000}},"accounts/fireworks/models/deepseek-v4-pro":{"id":"accounts/fireworks/models/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.15},"limit":{"context":1000000,"output":384000}}}},"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":{"k2p6":{"id":"k2p6","name":"Kimi K2.6","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04","last_updated":"2026-04","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}},"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}}}},"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}}}},"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":{"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax M2.7","family":"minimax-m2.7","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":204800,"output":131072},"provider":{"npm":"@ai-sdk/anthropic"}},"kimi-k2.5":{"id":"kimi-k2.5","name":"Kimi K2.5","family":"kimi-k2.5","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}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo V2.5 Pro","family":"mimo-v2.5-pro","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"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,"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":202752,"output":32768}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo V2 Omni","family":"mimo-v2-omni","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":128000},"status":"deprecated"},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo V2.5","family":"mimo-v2.5","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1000000,"output":128000}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05,"cache_write":0.625},"limit":{"context":262144,"output":65536},"provider":{"npm":"@ai-sdk/anthropic"}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-04","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202752,"output":32768}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.0028},"limit":{"context":1000000,"output":384000}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-10","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":65536}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.0145},"limit":{"context":1000000,"output":384000}},"minimax-m2.5":{"id":"minimax-m2.5","name":"MiniMax M2.5","family":"minimax-m2.5","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.03},"limit":{"context":204800,"output":65536}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo V2 Pro","family":"mimo-v2-pro","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":128000},"status":"deprecated"},"qwen3.5-plus":{"id":"qwen3.5-plus","name":"Qwen3.5 Plus","family":"qwen3.5","attachment":true,"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.2,"output":1.2,"cache_read":0.02,"cache_write":0.25},"limit":{"context":262144,"output":65536},"provider":{"npm":"@ai-sdk/anthropic"}}}},"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":{"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}},"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}},"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}},"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/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/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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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":{"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}},"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}},"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}},"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-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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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-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}},"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.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}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.276,"output":1.651,"cache_read":0.028,"cache_write":0.344},"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}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-14","last_updated":"2026-04-14","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.87,"output":3.48,"cache_read":0.17},"limit":{"context":202752,"output":128000}},"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}},"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}},"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-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-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}},"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-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-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-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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Moonshot Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":false,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.929,"output":3.858},"limit":{"context":262144,"output":16384}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-21","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.32,"output":7.9,"cache_read":0.132},"limit":{"context":245800,"output":65536}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"MiniMax/MiniMax-M2.7":{"id":"MiniMax/MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131072}},"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.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}},"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.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}},"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}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}}}},"firepass":{"id":"firepass","env":["FIREPASS_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.fireworks.ai/inference/v1/","name":"Fireworks (Firepass)","doc":"https://docs.fireworks.ai/firepass","models":{"accounts/fireworks/routers/kimi-k2p6-turbo":{"id":"accounts/fireworks/routers/kimi-k2p6-turbo","name":"Kimi K2.6 Turbo","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262000,"output":262000}}}},"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":{"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":{"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.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"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}}}},"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.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-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}},"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}},"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.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-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}},"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}},"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}},"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}},"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}},"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-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}},"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.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}},"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-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}},"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.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}},"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}},"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}},"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}},"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}},"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}},"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-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-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}},"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}},"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}},"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-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,"knowledge":"2025-05-31","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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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.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.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}},"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}},"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}},"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}},"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}},"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}},"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-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-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-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-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-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-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}},"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}},"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}}}},"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}}}},"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":{"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}},"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-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-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-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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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.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}},"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}}}},"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":{"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-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-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-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-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-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}},"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-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}},"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.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-03","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3.2,"cache_read":0.2},"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":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}},"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-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}},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi-K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-01","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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}},"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.7":{"id":"MiniMaxAI/MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"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-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1048576,"output":393216}}}},"zenmux":{"id":"zenmux","env":["ZENMUX_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://zenmux.ai/api/v1","name":"ZenMux","doc":"https://docs.zenmux.ai","models":{"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}},"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}},"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}},"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}},"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}},"kuaishou/kat-coder-pro-v2":{"id":"kuaishou/kat-coder-pro-v2","name":"KAT-Coder-Pro-V2","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":256000,"output":80000}},"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-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.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":{"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":{"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}},"x-ai/grok-4.2-fast":{"id":"x-ai/grok-4.2-fast","name":"Grok 4.2 Fast","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":9},"limit":{"context":2000000,"output":30000}},"x-ai/grok-4.2-fast-non-reasoning":{"id":"x-ai/grok-4.2-fast-non-reasoning","name":"Grok 4.2 Fast Non Reasoning","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":9},"limit":{"context":2000000,"output":30000}},"openai/gpt-5.3-chat":{"id":"openai/gpt-5.3-chat","name":"GPT-5.3 Chat","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.75,"output":14},"limit":{"context":128000,"output":16380},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.3-codex":{"id":"openai/gpt-5.3-codex","name":"GPT-5.3 Codex","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.75,"output":14},"limit":{"context":400000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.4-mini":{"id":"openai/gpt-5.4-mini","name":"GPT-5.4 Mini","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5},"limit":{"context":400000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"GPT-5.4 Nano","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25},"limit":{"context":400000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.4-pro":{"id":"openai/gpt-5.4-pro","name":"GPT-5.4 Pro","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":45,"output":225},"limit":{"context":1050000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.4":{"id":"openai/gpt-5.4","name":"GPT-5.4","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3.75,"output":18.75},"limit":{"context":1050000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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,"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,"output":0},"limit":{"context":200000,"output":64000}},"z-ai/glm-5v-turbo":{"id":"z-ai/glm-5v-turbo","name":"GLM 5V Turbo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.726,"output":3.1946,"cache_read":0.1743},"limit":{"context":200000,"output":128000}},"z-ai/glm-4.7":{"id":"z-ai/glm-4.7","name":"GLM 4.7","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"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-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.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-5.1":{"id":"z-ai/glm-5.1","name":"GLM-5.1","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-03","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8781,"output":3.5126,"cache_read":0.1903},"limit":{"context":200000,"output":131072}},"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.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.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-5-turbo":{"id":"z-ai/glm-5-turbo","name":"GLM 5 Turbo","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.88,"output":3.48},"limit":{"context":200000,"output":128000}},"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.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}},"volcengine/doubao-seed-2.0-code":{"id":"volcengine/doubao-seed-2.0-code","name":"Doubao Seed 2.0 Code","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.9,"output":4.48},"limit":{"context":256000,"output":32000}},"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-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-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}},"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}},"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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax M2.7","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3055,"output":1.2219},"limit":{"context":204800,"output":131070},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"minimax/minimax-m2.7-highspeed":{"id":"minimax/minimax-m2.7-highspeed","name":"MiniMax M2.7 highspeed","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.611,"output":2.4439},"limit":{"context":204800,"output":131070},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"minimax/minimax-m2.5-lightning":{"id":"minimax/minimax-m2.5-lightning","name":"MiniMax M2.5 highspeed","attachment":false,"reasoning":true,"tool_call":true,"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"minimax/minimax-m2.5":{"id":"minimax/minimax-m2.5","name":"MiniMax M2.5","attachment":false,"reasoning":true,"tool_call":true,"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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}},"qwen/qwen3.5-flash":{"id":"qwen/qwen3.5-flash","name":"Qwen3.5 Flash","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.1,"output":0.4},"limit":{"context":1020000,"output":1020000}},"qwen/qwen3.6-plus":{"id":"qwen/qwen3.6-plus","name":"Qwen3.6-Plus","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05,"cache_write":0.625,"context_over_200k":{"input":2,"output":6,"cache_read":0.2,"cache_write":2.5}},"limit":{"context":1000000,"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.5-plus":{"id":"qwen/qwen3.5-plus","name":"Qwen3.5 Plus","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":4.8},"limit":{"context":1000000,"output":64000}},"google/gemini-3.1-flash-lite-preview":{"id":"google/gemini-3.1-flash-lite-preview","name":"Gemini 3.1 Flash Lite Preview","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-03-20","last_updated":"2025-03-20","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1.5},"limit":{"context":1050000,"output":65530}},"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-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-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}},"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-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}},"sapiens-ai/agnes-1.5-lite":{"id":"sapiens-ai/agnes-1.5-lite","name":"Agnes 1.5 Lite","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-26","last_updated":"2026-03-26","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.12,"output":0.6},"limit":{"context":256000,"output":256000}},"sapiens-ai/agnes-1.5-pro":{"id":"sapiens-ai/agnes-1.5-pro","name":"Agnes 1.5 Pro","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-21","last_updated":"2026-03-21","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.16,"output":0.8},"limit":{"context":256000,"output":256000}},"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":false,"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-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}},"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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":false,"knowledge":"2025-01-01","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262140,"output":262140}},"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}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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-05-31","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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"anthropic/claude-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Claude Opus 4.7","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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":32000},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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-08-31","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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"deepseek/deepseek-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}},"tencent/hy3-preview":{"id":"tencent/hy3-preview","name":"Hy3 preview","family":"Hy","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.172,"output":0.572,"cache_read":0.058,"cache_write":0},"limit":{"context":256000,"output":64000}},"openai/gpt-5.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"experimental":{"modes":{"fast":{"cost":{"input":12.5,"output":75,"cache_read":1.25},"provider":{"body":{"service_tier":"priority"}}}}}},"openai/gpt-5.5-pro":{"id":"openai/gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"output":128000}},"xiaomi/mimo-v2.5-pro":{"id":"xiaomi/mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-omni":{"id":"xiaomi/mimo-v2-omni","name":"MiMo V2 Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":265000,"output":265000}},"xiaomi/mimo-v2.5":{"id":"xiaomi/mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-pro":{"id":"xiaomi/mimo-v2-pro","name":"MiMo V2 Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1000000,"output":256000}},"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-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":262144,"output":65536}}}},"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}}}},"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":{"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-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-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-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-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-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-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-qwen-32b":{"id":"deepseek/deepseek-r1-distill-qwen-32b","name":"DeepSeek R1 Distill Qwen 32B","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-01-20","last_updated":"2025-01-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.3},"limit":{"context":64000,"output":32000}},"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}},"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-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-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-r1-distill-qwen-14b":{"id":"deepseek/deepseek-r1-distill-qwen-14b","name":"DeepSeek R1 Distill Qwen 14B","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-01-20","last_updated":"2025-01-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.15},"limit":{"context":32768,"output":16384}},"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-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}},"inclusionai/ling-2.6-1t":{"id":"inclusionai/ling-2.6-1t","name":"Ling-2.6-1T","family":"ling","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"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}},"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}},"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/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-5.1":{"id":"zai-org/glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"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,"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.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.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.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.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}},"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/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}},"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}},"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}},"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.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-3.2-3b-instruct":{"id":"meta-llama/llama-3.2-3b-instruct","name":"Llama 3.2 3B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-09-18","last_updated":"2024-09-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.03,"output":0.05},"limit":{"context":32768,"output":32000}},"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-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-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-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}},"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}},"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-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/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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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-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-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}},"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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax M2.7","family":"minimax-m2.7","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"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.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.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}},"minimax/minimax-m2.5-highspeed":{"id":"minimax/minimax-m2.5-highspeed","name":"MiniMax M2.5 Highspeed","family":"minimax-m2.5","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.6,"output":2.4,"cache_read":0.03},"limit":{"context":204800,"output":131100}},"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.5-122b-a10b":{"id":"qwen/qwen3.5-122b-a10b","name":"Qwen3.5-122B-A10B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":3.2},"limit":{"context":262144,"output":65536}},"qwen/qwen3.6-27b":{"id":"qwen/qwen3.6-27b","name":"Qwen3.6-27B","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":65536}},"qwen/qwen3.5-27b":{"id":"qwen/qwen3.5-27b","name":"Qwen3.5-27B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.4},"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,"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-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/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-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-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-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}},"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-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-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-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/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/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-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-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-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-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-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-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-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-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-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.5-35b-a3b":{"id":"qwen/qwen3.5-35b-a3b","name":"Qwen3.5-35B-A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2},"limit":{"context":262144,"output":65536}},"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}},"google/gemma-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Gemma 4 31B","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.4},"limit":{"context":262144,"output":131072}},"google/gemma-3-12b-it":{"id":"google/gemma-3-12b-it","name":"Gemma 3 12B","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.05,"output":0.1},"limit":{"context":131072,"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,"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}},"google/gemma-4-26b-a4b-it":{"id":"google/gemma-4-26b-a4b-it","name":"Gemma 4 26B A4B","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.13,"output":0.4},"limit":{"context":262144,"output":131072}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"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}},"deepseek/deepseek-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1048576,"output":393216}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.69,"output":3.38,"cache_read":0.13},"limit":{"context":1048576,"output":393216}}}},"xiaomi-token-plan-cn":{"id":"xiaomi-token-plan-cn","env":["XIAOMI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://token-plan-cn.xiaomimimo.com/v1","name":"Xiaomi Token Plan (China)","doc":"https://platform.xiaomimimo.com/#/docs","models":{"mimo-v2-tts":{"id":"mimo-v2-tts","name":"MiMo-V2-TTS","family":"mimo","attachment":false,"reasoning":false,"tool_call":false,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["audio"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":8192,"output":16384}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262144,"output":131072}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"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,"output":0,"cache_read":0},"limit":{"context":262144,"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":{"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-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}},"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}},"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}},"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.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}},"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.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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"zai-org/GLM-5.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.4,"output":4.4,"cache_read":0.26,"cache_write":0},"limit":{"context":200000,"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":{"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}},"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-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.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/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-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}},"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-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-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-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-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-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-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/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-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/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}},"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.39,"output":2.34,"cache_read":0.195},"limit":{"context":262144,"output":65536}},"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/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/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}},"zai-org/GLM-5.1-TEE":{"id":"zai-org/GLM-5.1-TEE","name":"GLM 5.1 TEE","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":3.15,"cache_read":0.475},"limit":{"context":202752,"output":65535}},"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.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-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-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-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-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.95,"output":3.15,"cache_read":0.475},"limit":{"context":202752,"output":65535}},"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-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.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,"cache_read":0.15},"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}},"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.4,"output":1.7,"cache_read":0.2},"limit":{"context":202752,"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}},"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}},"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}},"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}},"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-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-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-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}},"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.28,"output":0.42,"cache_read":0.14},"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}},"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}},"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/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}},"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/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/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-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}},"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}},"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.6-TEE":{"id":"moonshotai/Kimi-K2.6-TEE","name":"Kimi K2.6 TEE","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-12","release_date":"2026-04-20","last_updated":"2026-04-23","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.44,"output":2},"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}},"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}},"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.3,"output":1.1,"cache_read":0.15},"limit":{"context":196608,"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}},"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/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/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/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}}}},"dinference":{"id":"dinference","env":["DINFERENCE_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.dinference.com/v1","name":"DInference","doc":"https://dinference.com","models":{"gpt-oss-120b":{"id":"gpt-oss-120b","name":"GPT OSS 120B","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-08","last_updated":"2025-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.0675,"output":0.27},"limit":{"context":131072,"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.45,"output":1.65},"limit":{"context":200000,"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":true,"cost":{"input":0.75,"output":2.4},"limit":{"context":200000,"output":128000}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":3.89},"limit":{"context":200000,"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.22,"output":0.88},"limit":{"context":200000,"output":32000}}}},"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":{"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}},"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},"provider":{"npm":"@ai-sdk/openai-compatible"}},"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},"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"}},"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.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000},"provider":{"npm":"@ai-sdk/openai-compatible"}},"gpt-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000},"provider":{"npm":"@ai-sdk/openai-compatible"}},"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}},"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},"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}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"provider":{"npm":"@ai-sdk/openai-compatible"}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000},"provider":{"npm":"@ai-sdk/openai-compatible"}}}},"deepinfra":{"id":"deepinfra","env":["DEEPINFRA_API_KEY"],"npm":"@ai-sdk/deepinfra","name":"Deep Infra","doc":"https://deepinfra.com/models","models":{"Qwen/Qwen3.5-397B-A17B":{"id":"Qwen/Qwen3.5-397B-A17B","name":"Qwen 3.5 397B A17B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-02-01","last_updated":"2026-04-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.54,"output":3.4},"limit":{"context":262144,"output":81920}},"Qwen/Qwen3.5-35B-A3B":{"id":"Qwen/Qwen3.5-35B-A3B","name":"Qwen 3.5 35B A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-02-01","last_updated":"2026-04-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.95},"limit":{"context":262144,"output":81920}},"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}},"Qwen/Qwen3.6-35B-A3B":{"id":"Qwen/Qwen3.6-35B-A3B","name":"Qwen3.6 35B A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":1},"limit":{"context":262144,"output":81920}},"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.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-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-5.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202752,"output":16384}},"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}},"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.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}},"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-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-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-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.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}},"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.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}},"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}},"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}},"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}},"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}},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.75,"output":3.5,"cache_read":0.15},"limit":{"context":262144,"output":16384}},"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}},"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.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.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}},"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}},"deepseek-ai/DeepSeek-V4-Flash":{"id":"deepseek-ai/DeepSeek-V4-Flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek-ai/DeepSeek-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":65536,"output":65536}},"google/gemma-4-26B-A4B-it":{"id":"google/gemma-4-26B-A4B-it","name":"Gemma 4 26B","family":"gemma","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.07,"output":0.34},"limit":{"context":256000,"output":8192}},"google/gemma-4-31B-it":{"id":"google/gemma-4-31B-it","name":"Gemma 4 31B","family":"gemma","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.13,"output":0.38},"limit":{"context":256000,"output":8192}}}},"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":{"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-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-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}},"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}},"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-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}},"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.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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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.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.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}},"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}},"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}},"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-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-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-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}},"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}},"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}},"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}},"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/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-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}},"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.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.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}},"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}},"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-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-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}},"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}},"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}},"mimo-v2-flash":{"id":"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-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":256000}},"xiaomi/mimo-v2-flash":{"id":"xiaomi/mimo-v2-flash","name":"Xiaomi/Mimo-V2-Flash","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"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":256000}}}},"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":{"rekaai/reka-edge":{"id":"rekaai/reka-edge","name":"Reka Edge","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-20","last_updated":"2026-04-11","modalities":{"input":["image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":16384,"output":16384}},"rekaai/reka-flash-3":{"id":"rekaai/reka-flash-3","name":"Reka Flash 3","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2025-03-12","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.2},"limit":{"context":65536,"output":65536}},"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}},"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}},"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}},"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}},"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}},"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}},"ibm-granite/granite-4.1-8b":{"id":"ibm-granite/granite-4.1-8b","name":"IBM: Granite 4.1 8B","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-30","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.05,"output":0.1,"cache_read":0.05},"limit":{"context":131072,"output":131072}},"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}},"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}},"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-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":{"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-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}},"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}},"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":{"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-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-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-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}},"deepseek/deepseek-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.14,"output":0.28,"cache_read":0.0028},"limit":{"context":1048576,"output":384000}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek: DeepSeek V4 Pro","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.435,"output":0.87,"cache_read":0.003625},"limit":{"context":1048576,"output":384000}},"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-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-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-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}},"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/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"},"openrouter/owl-alpha":{"id":"openrouter/owl-alpha","name":"Owl Alpha","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":1048756,"output":262144},"status":"alpha"},"openrouter/pareto-code":{"id":"openrouter/pareto-code","name":"Pareto Code Router","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-04-21","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":200000,"output":65536}},"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}},"inclusionai/ling-2.6-1t:free":{"id":"inclusionai/ling-2.6-1t:free","name":"inclusionAI: Ling-2.6-1T (free)","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"inclusionai/ling-2.6-flash":{"id":"inclusionai/ling-2.6-flash","name":"inclusionAI: Ling-2.6 Flash","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.08,"output":0.24,"cache_read":0.016},"limit":{"context":262144,"output":32768}},"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/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-large-thinking":{"id":"arcee-ai/trinity-large-thinking","name":"Arcee AI: Trinity Large Thinking","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.22,"output":0.85},"limit":{"context":262144,"output":262144}},"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}},"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/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/trinity-large-preview":{"id":"arcee-ai/trinity-large-preview","name":"Arcee AI: Trinity Large Preview","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-01-28","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.45},"limit":{"context":131000,"output":32768}},"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}},"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}},"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}},"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-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}},"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-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}},"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}},"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}},"~google/gemini-pro-latest":{"id":"~google/gemini-pro-latest","name":"Google: Gemini Pro Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":12,"cache_read":0.2,"cache_write":0.375},"limit":{"context":1048576,"output":65536}},"~google/gemini-flash-latest":{"id":"~google/gemini-flash-latest","name":"Google: Gemini Flash Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05,"cache_write":0.08333333333333334},"limit":{"context":1048576,"output":65536}},"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/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}},"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/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}},"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}},"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}},"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}},"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}},"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}},"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":"2025-08-26","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"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}},"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}},"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/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-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/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-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-medium-3-5":{"id":"mistralai/mistral-medium-3-5","name":"Mistral: Mistral Medium 3.5","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-30","last_updated":"2026-05-07","modalities":{"input":["image","text"],"output":["text"]},"open_weights":false,"cost":{"input":1.5,"output":7.5},"limit":{"context":262144,"output":262144}},"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/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/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/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/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/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-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/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/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}},"mistralai/mistral-small-2603":{"id":"mistralai/mistral-small-2603","name":"Mistral: Mistral Small 4","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6,"cache_read":0.015},"limit":{"context":262144,"output":262144}},"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/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/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":{"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-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/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-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/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/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}},"~anthropic/claude-haiku-latest":{"id":"~anthropic/claude-haiku-latest","name":"Anthropic: Claude Haiku Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-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}},"~anthropic/claude-sonnet-latest":{"id":"~anthropic/claude-sonnet-latest","name":"Anthropic: Claude Sonnet Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","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":128000}},"~anthropic/claude-opus-latest":{"id":"~anthropic/claude-opus-latest","name":"Anthropic: Claude Opus Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-16","last_updated":"2026-05-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":1000000,"output":128000}},"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-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-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-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.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-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.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.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.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-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-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-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}},"x-ai/grok-4.20":{"id":"x-ai/grok-4.20","name":"xAI: Grok 4.20","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-31","last_updated":"2026-04-11","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6,"cache_read":0.2},"limit":{"context":2000000,"output":2000000}},"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.3":{"id":"x-ai/grok-4.3","name":"xAI: Grok 4.3","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2},"limit":{"context":1000000,"output":4096}},"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-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-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}},"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-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-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-4.20-multi-agent":{"id":"x-ai/grok-4.20-multi-agent","name":"xAI: Grok 4.20 Multi-Agent","attachment":true,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-31","last_updated":"2026-04-11","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6,"cache_read":0.2},"limit":{"context":2000000,"output":2000000}},"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}},"tencent/hy3-preview:free":{"id":"tencent/hy3-preview:free","name":"Tencent: Hy3 Preview (free)","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"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}},"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}},"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-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.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}},"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.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}},"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}},"microsoft/phi-4-mini-instruct":{"id":"microsoft/phi-4-mini-instruct","name":"Microsoft: Phi 4 Mini Instruct","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-10-17","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.08,"output":0.35,"cache_read":0.08},"limit":{"context":128000,"output":128000}},"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}},"poolside/laguna-m.1:free":{"id":"poolside/laguna-m.1:free","name":"Poolside: Laguna M.1 (free)","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":8192}},"poolside/laguna-xs.2:free":{"id":"poolside/laguna-xs.2:free","name":"Poolside: Laguna XS.2 (free)","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":8192}},"cohere/command-r7b-12-2024":{"id":"cohere/command-r7b-12-2024","name":"Cohere: Command R7B (12-2024)","attachment":false,"reasoning":false,"tool_call":true,"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}},"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-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}},"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}},"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-3-super-120b-a12b":{"id":"nvidia/nemotron-3-super-120b-a12b","name":"NVIDIA: Nemotron 3 Super","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-11","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.5,"cache_read":0.1},"limit":{"context":262144,"output":262144}},"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free":{"id":"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free","name":"NVIDIA: Nemotron 3 Nano Omni (free)","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-05-01","modalities":{"input":["text","audio","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":256000,"output":65536}},"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}},"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/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/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}},"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}},"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-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/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-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-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-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-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/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-chat-latest":{"id":"openai/gpt-chat-latest","name":"OpenAI: GPT Chat Latest","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"release_date":"2026-05-05","last_updated":"2026-05-07","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":400000,"output":128000}},"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-5.5":{"id":"openai/gpt-5.5","name":"OpenAI: GPT-5.5","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-24","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":1050000,"output":128000}},"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-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-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-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/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.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/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/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/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/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.4-mini":{"id":"openai/gpt-5.4-mini","name":"OpenAI: GPT-5.4 Mini","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-17","last_updated":"2026-04-11","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"output":128000}},"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/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-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"OpenAI: GPT-5.4 Nano","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-17","last_updated":"2026-04-11","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"output":128000}},"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-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.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-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-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-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/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.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-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/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/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-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/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.4-image-2":{"id":"openai/gpt-5.4-image-2","name":"OpenAI: GPT-5.4 Image 2","attachment":true,"reasoning":true,"tool_call":false,"temperature":false,"release_date":"2026-04-21","last_updated":"2026-05-01","modalities":{"input":["image","text","pdf"],"output":["image","text"]},"open_weights":false,"cost":{"input":8,"output":15,"cache_read":2},"limit":{"context":272000,"output":128000}},"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-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-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.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-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/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}},"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/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/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-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-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-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-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-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-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":{"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/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/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.5-pro":{"id":"openai/gpt-5.5-pro","name":"OpenAI: GPT-5.5 Pro","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-24","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180},"limit":{"context":1050000,"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/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/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-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-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}},"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-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}},"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-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}},"z-ai/glm-5v-turbo":{"id":"z-ai/glm-5v-turbo","name":"Z.ai: GLM 5V Turbo","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-11","modalities":{"input":["image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":202752,"output":131072}},"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-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-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-5.1":{"id":"z-ai/glm-5.1","name":"Z.ai: GLM 5.1","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.26,"output":3.96},"limit":{"context":202752,"output":131072}},"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.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-5-turbo":{"id":"z-ai/glm-5-turbo","name":"Z.ai: GLM 5 Turbo","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-15","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":202752,"output":131072}},"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.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.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}},"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}},"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/qianfan-ocr-fast:free":{"id":"baidu/qianfan-ocr-fast:free","name":"Baidu: Qianfan-OCR-Fast (free)","attachment":true,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-05-01","modalities":{"input":["image","text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":65536,"output":28672}},"baidu/cobuddy:free":{"id":"baidu/cobuddy:free","name":"Baidu: CoBuddy (free)","attachment":false,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-05-06","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":65536}},"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":{"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}},"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-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}},"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}},"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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax: MiniMax M2.7","family":"minimax-m2.7","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"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-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-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-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":{"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}},"~openai/gpt-latest":{"id":"~openai/gpt-latest","name":"OpenAI: GPT Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":1050000,"output":128000}},"~openai/gpt-mini-latest":{"id":"~openai/gpt-mini-latest","name":"OpenAI: GPT Mini Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"output":128000}},"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.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-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/qwen3.6-27b":{"id":"qwen/qwen3.6-27b","name":"Qwen: Qwen3.6 27B","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.325,"output":3.25},"limit":{"context":256000,"output":65536}},"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/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-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.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-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/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/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/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/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.6-35b-a3b":{"id":"qwen/qwen3.6-35b-a3b","name":"Qwen: Qwen3.6 35B A3B","attachment":true,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.1612,"output":0.96525,"cache_read":0.1612},"limit":{"context":262144,"output":65536}},"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-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-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-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}},"qwen/qwen3.6-plus":{"id":"qwen/qwen3.6-plus","name":"Qwen: Qwen3.6 Plus","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-08-26","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["text"]},"open_weights":false,"cost":{"input":0.325,"output":1.95,"cache_read":0.0325,"cache_write":0.40625},"limit":{"context":1000000,"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/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-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/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-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-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/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-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-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-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-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-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.5-plus-20260420":{"id":"qwen/qwen3.5-plus-20260420","name":"Qwen: Qwen3.5 Plus 2026-04-20","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2.4},"limit":{"context":1000000,"output":65536}},"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-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-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-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-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/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-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/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.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/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/qwen3.6-max-preview":{"id":"qwen/qwen3.6-max-preview","name":"Qwen: Qwen3.6 Max Preview","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.04,"output":6.24,"cache_write":1.3},"limit":{"context":262144,"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/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-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/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.6-flash":{"id":"qwen/qwen3.6-flash","name":"Qwen: Qwen3.6 Flash","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1.5,"cache_write":0.3125},"limit":{"context":1000000,"output":65536}},"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}},"kwaipilot/kat-coder-pro-v2":{"id":"kwaipilot/kat-coder-pro-v2","name":"Kwaipilot: KAT-Coder-Pro V2","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":256000,"output":80000}},"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/lyria-3-clip-preview":{"id":"google/lyria-3-clip-preview","name":"Google: Lyria 3 Clip Preview","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["audio","text"]},"open_weights":false,"cost":{"input":0,"output":0},"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-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-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/lyria-3-pro-preview":{"id":"google/lyria-3-pro-preview","name":"Google: Lyria 3 Pro Preview","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["audio","text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":1048576,"output":65536}},"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.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.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-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/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}},"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-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Google: Gemma 4 31B","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-11","modalities":{"input":["image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.4},"limit":{"context":262144,"output":131072}},"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/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/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-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/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/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/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/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/gemma-4-26b-a4b-it":{"id":"google/gemma-4-26b-a4b-it","name":"Google: Gemma 4 26B A4B","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-03","last_updated":"2026-04-11","modalities":{"input":["image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.12,"output":0.4},"limit":{"context":262144,"output":262144}},"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-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}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"MoonshotAI: Kimi K2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"structured_output":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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-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}},"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}},"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-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-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}},"~moonshotai/kimi-latest":{"id":"~moonshotai/kimi-latest","name":"MoonshotAI: Kimi Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.74,"output":3.49,"cache_read":0.14},"limit":{"context":262142,"output":262142}},"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/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/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}},"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}},"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.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.6-fast":{"id":"anthropic/claude-opus-4.6-fast","name":"Anthropic: Claude Opus 4.6 (Fast)","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-05-31","release_date":"2026-04-07","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":150,"cache_read":3,"cache_write":37.5},"limit":{"context":1000000,"output":128000}},"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.6":{"id":"anthropic/claude-opus-4.6","name":"Anthropic: Claude Opus 4.6","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"],"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-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Anthropic: Claude Opus 4.7","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-16","last_updated":"2026-05-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":1000000,"output":128000}},"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.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-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-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-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-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-sonnet-4.6":{"id":"anthropic/claude-sonnet-4.6","name":"Anthropic: Claude Sonnet 4.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","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}},"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}},"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}},"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}},"xiaomi/mimo-v2.5-pro":{"id":"xiaomi/mimo-v2.5-pro","name":"Xiaomi: MiMo V2.5 Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-omni":{"id":"xiaomi/mimo-v2-omni","name":"Xiaomi: MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":65536}},"xiaomi/mimo-v2.5":{"id":"xiaomi/mimo-v2.5","name":"Xiaomi: MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-pro":{"id":"xiaomi/mimo-v2-pro","name":"Xiaomi: MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-flash":{"id":"xiaomi/mimo-v2-flash","name":"Xiaomi: MiMo-V2-Flash","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"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.09,"output":0.29,"cache_read":0.045},"limit":{"context":262144,"output":65536}}}},"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.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-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}},"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-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}},"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}},"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,"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}},"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-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}},"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-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-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-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}},"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}},"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-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-05","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}},"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-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-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-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-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-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}},"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}},"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-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}},"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,"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}},"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.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}}}},"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}}}},"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/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/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/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/zai-org/glm-4.7-flash":{"id":"workers-ai/@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}},"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/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/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/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}},"workers-ai/@cf/nvidia/nemotron-3-120b-a12b":{"id":"workers-ai/@cf/nvidia/nemotron-3-120b-a12b","name":"Nemotron 3 Super 120B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":1.5},"limit":{"context":256000,"output":256000}},"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/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/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/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/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-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-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/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.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.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.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.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.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-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-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/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/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/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/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/moonshotai/kimi-k2.5":{"id":"workers-ai/@cf/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"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3,"cache_read":0.1},"limit":{"context":256000,"output":256000}},"workers-ai/@cf/moonshotai/kimi-k2.6":{"id":"workers-ai/@cf/moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":256000,"output":256000}},"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/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/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-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/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/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/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}},"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-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-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-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/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.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/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/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-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/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-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/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-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.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}},"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-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-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-opus-4-7":{"id":"anthropic/claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01","release_date":"2026-04-16","last_updated":"2026-04-16","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"}},"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-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-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-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-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-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":{"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-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-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,"context_over_200k":{"input":10,"output":37.5,"cache_read":1,"cache_write":12.5}},"limit":{"context":1000000,"output":128000}},"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-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}},"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-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}},"openai/gpt-5.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000}}}},"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.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":400000,"input":128000,"output":128000},"status":"deprecated"},"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"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":144000,"input":128000,"output":64000}},"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,"input":128000,"output":64000}},"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,"input":128000,"output":64000}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":false,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":400000,"input":272000,"output":128000}},"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":264000,"input":128000,"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","audio","video"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":128000,"input":128000,"output":64000},"status":"deprecated"},"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}},"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,"input":128000,"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":0,"output":0},"limit":{"context":264000,"input":128000,"output":64000}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":400000,"input":272000,"output":128000}},"claude-opus-4.7":{"id":"claude-opus-4.7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":144000,"input":128000,"output":64000}},"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}},"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":400000,"input":128000,"output":128000},"status":"deprecated"},"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":216000,"input":128000,"output":16000},"status":"deprecated"},"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,"input":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":264000,"input":128000,"output":64000},"status":"deprecated"},"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":144000,"input":128000,"output":32000}},"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},"status":"deprecated"},"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":160000,"input":128000,"output":32000}},"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-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":128000,"input":64000,"output":4096}},"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},"status":"deprecated"},"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":144000,"input":128000,"output":32000}},"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":128000,"input":64000,"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-31","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":200000,"input":128000,"output":32000}},"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":400000,"input":128000,"output":128000},"status":"deprecated"}}},"mixlayer":{"id":"mixlayer","env":["MIXLAYER_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://models.mixlayer.ai/v1","name":"Mixlayer","doc":"https://docs.mixlayer.com","models":{"qwen/qwen3.5-122b-a10b":{"id":"qwen/qwen3.5-122b-a10b","name":"Qwen3.5 122B A10B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":3.2},"limit":{"context":262144,"output":262144}},"qwen/qwen3.5-27b":{"id":"qwen/qwen3.5-27b","name":"Qwen3.5 27B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.4},"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-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":262144}},"qwen/qwen3.5-9b":{"id":"qwen/qwen3.5-9b","name":"Qwen3.5 9B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.4},"limit":{"context":262144,"output":262144}},"qwen/qwen3.5-35b-a3b":{"id":"qwen/qwen3.5-35b-a3b","name":"Qwen3.5 35B A3B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":1.3},"limit":{"context":262144,"output":262144}}}},"xiaomi-token-plan-sgp":{"id":"xiaomi-token-plan-sgp","env":["XIAOMI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://token-plan-sgp.xiaomimimo.com/v1","name":"Xiaomi Token Plan (Singapore)","doc":"https://platform.xiaomimimo.com/#/docs","models":{"mimo-v2-tts":{"id":"mimo-v2-tts","name":"MiMo-V2-TTS","family":"mimo","attachment":false,"reasoning":false,"tool_call":false,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["audio"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":8192,"output":16384}},"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,"output":0,"cache_read":0},"limit":{"context":262144,"output":65536}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262144,"output":131072}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}}}},"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-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_read":0.24,"cache_write":0},"limit":{"context":200000,"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-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.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-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.4,"output":4.4,"cache_read":0.26,"cache_write":0},"limit":{"context":200000,"output":131072}},"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}},"glm-5-turbo":{"id":"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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_read":0.24,"cache_write":0},"limit":{"context":200000,"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.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.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.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}}}},"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":{"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-01","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"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"}},"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"}},"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}},"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},"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}},"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.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"}},"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-08-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"}},"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-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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"}},"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},"provider":{"npm":"@ai-sdk/openai"}},"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"}},"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"}},"ring-2.6-1t-free":{"id":"ring-2.6-1t-free","name":"Ring 2.6 1T Free","family":"ring-1t-free","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-06","release_date":"2026-05-08","last_updated":"2026-05-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262000,"output":66000}},"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"}},"big-pickle":{"id":"big-pickle","name":"Big Pickle","family":"big-pickle","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":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}},"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"}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05,"cache_write":0.625},"limit":{"context":262144,"output":65536},"provider":{"npm":"@ai-sdk/anthropic"}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000},"provider":{"npm":"@ai-sdk/openai"}},"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},"status":"deprecated","provider":{"npm":"@ai-sdk/anthropic"}},"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},"status":"deprecated"},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-04","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":204800,"output":131072}},"gpt-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000},"provider":{"npm":"@ai-sdk/openai"}},"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"}},"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"}},"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"}},"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"}},"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"},"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.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"}},"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"},"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"}},"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"}},"qwen3.6-plus-free":{"id":"qwen3.6-plus-free","name":"Qwen3.6 Plus Free","family":"qwen-free","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":1048576,"output":64000},"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},"status":"deprecated"},"ling-2.6-flash-free":{"id":"ling-2.6-flash-free","name":"Ling 2.6 Flash Free","family":"ling-flash-free","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262100,"output":32800},"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},"status":"deprecated","provider":{"npm":"@ai-sdk/google"}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-10","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":65536}},"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"}},"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"},"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},"status":"deprecated"},"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"}},"hy3-preview-free":{"id":"hy3-preview-free","name":"Hy3 preview Free","family":"hy3-free","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":256000,"output":64000},"status":"deprecated"},"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}},"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"},"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"}},"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"},"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"}},"qwen3.5-plus":{"id":"qwen3.5-plus","name":"Qwen3.5 Plus","family":"qwen3.5","attachment":true,"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.2,"output":1.2,"cache_read":0.02,"cache_write":0.25},"limit":{"context":262144,"output":65536},"provider":{"npm":"@ai-sdk/anthropic"}},"mimo-v2-pro-free":{"id":"mimo-v2-pro-free","name":"MiMo V2 Pro Free","family":"mimo-pro-free","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":1048576,"output":64000},"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":204800,"output":128000}},"gpt-5.5-pro":{"id":"gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-24","last_updated":"2026-04-24","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"}},"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.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"}},"mimo-v2-omni-free":{"id":"mimo-v2-omni-free","name":"MiMo V2 Omni Free","family":"mimo-omni-free","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262144,"output":64000},"status":"deprecated"},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"provider":{"npm":"@ai-sdk/openai"}},"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},"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-05-31","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},"provider":{"npm":"@ai-sdk/anthropic"}}}},"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-2603":{"id":"step-3.5-flash-2603","name":"Step 3.5 Flash 2603","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.3,"cache_read":0.02},"limit":{"context":256000,"input":256000,"output":256000}},"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}},"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}}}},"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":{"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}},"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}},"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.5-397B-A17B":{"id":"Qwen/Qwen3.5-397B-A17B","name":"Qwen3.5-397B-A17B","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-07-15","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6,"cache_read":0.06,"cache_write":0.75},"limit":{"context":262144,"input":250000,"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-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-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}},"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-fast":{"id":"Qwen/Qwen3-235B-A22B-Thinking-2507-fast","name":"Qwen3-235B-A22B-Thinking-2507-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-07-25","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":2,"cache_read":0.05,"cache_write":0.625},"limit":{"context":8000,"input":7000,"output":8192}},"Qwen/Qwen3.5-397B-A17B-fast":{"id":"Qwen/Qwen3.5-397B-A17B-fast","name":"Qwen3.5-397B-A17B-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-07-15","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6,"cache_read":0.06,"cache_write":0.75},"limit":{"context":8000,"input":7000,"output":8192}},"Qwen/Qwen3-Next-80B-A3B-Thinking-fast":{"id":"Qwen/Qwen3-Next-80B-A3B-Thinking-fast","name":"Qwen3-Next-80B-A3B-Thinking-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-07-25","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":1.2,"cache_read":0.015,"cache_write":0.1875},"limit":{"context":8000,"input":7000,"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}},"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}},"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}},"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}},"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}},"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/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}},"nvidia/Nemotron-3-Nano-Omni":{"id":"nvidia/Nemotron-3-Nano-Omni","name":"Nemotron-3-Nano-Omni","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2025-01-20","last_updated":"2026-05-07","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":65536,"input":60000,"output":8192}},"deepseek-ai/DeepSeek-V3.2-fast":{"id":"deepseek-ai/DeepSeek-V3.2-fast","name":"DeepSeek-V3.2-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2025-01-27","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.04,"cache_write":0.5},"limit":{"context":8000,"input":7000,"output":8192}},"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}},"openai/gpt-oss-120b-fast":{"id":"openai/gpt-oss-120b-fast","name":"gpt-oss-120b-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-06","release_date":"2025-06-10","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.5,"cache_read":0.01,"cache_write":0.125},"limit":{"context":8000,"input":7000,"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}},"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":{"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}},"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}},"MiniMaxAI/MiniMax-M2.5":{"id":"MiniMaxAI/MiniMax-M2.5","name":"MiniMax-M2.5","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2025-01-20","last_updated":"2026-05-07","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":196608,"input":190000,"output":8192}},"MiniMaxAI/MiniMax-M2.5-fast":{"id":"MiniMaxAI/MiniMax-M2.5-fast","name":"MiniMax-M2.5-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2025-01-20","last_updated":"2026-05-07","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":8000,"input":7000,"output":8192}},"deepseek-ai/DeepSeek-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.75,"output":3.5,"cache_read":0.15},"limit":{"context":1000000,"output":384000}}}},"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":{"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}},"novita/kimi-k2.5":{"id":"novita/kimi-k2.5","name":"Kimi-K2.5","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":3,"cache_read":0.1},"limit":{"context":128000,"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},"status":"deprecated"},"novita/glm-5":{"id":"novita/glm-5","name":"GLM-5","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-02-15","last_updated":"2026-02-15","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3.2,"cache_read":0.2},"limit":{"context":205000,"output":131072}},"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/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/kimi-k2.6":{"id":"novita/kimi-k2.6","name":"Kimi-K2.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-20","last_updated":"2026-05-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.96,"output":4.04,"cache_read":0.16},"limit":{"context":262144,"input":262144,"output":262144}},"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}},"novita/deepseek-v3.2":{"id":"novita/deepseek-v3.2","name":"DeepSeek-V3.2","attachment":true,"reasoning":true,"tool_call":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.13},"limit":{"context":128000,"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/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}},"fireworks-ai/kimi-k2.5-fw":{"id":"fireworks-ai/kimi-k2.5-fw","name":"Kimi-K2.5-FW","attachment":true,"reasoning":false,"tool_call":true,"temperature":false,"release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"input":245760,"output":16384}},"empiriolabs/deepseek-v4-pro-el":{"id":"empiriolabs/deepseek-v4-pro-el","name":"DeepSeek-V4-Pro-EL","attachment":true,"reasoning":true,"tool_call":true,"release_date":"2026-04-24","last_updated":"2026-05-02","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.67,"output":3.33},"limit":{"context":1000000,"input":1000000,"output":384000}},"empiriolabs/deepseek-v4-flash-el":{"id":"empiriolabs/deepseek-v4-flash-el","name":"DeepSeek-V4-Flash-EL","attachment":true,"reasoning":true,"tool_call":true,"release_date":"2026-04-24","last_updated":"2026-05-02","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28},"limit":{"context":1000000,"input":1000000,"output":384000}},"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}},"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-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}},"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,"cost":{"input":0.35,"output":0.75},"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,"cost":{"input":0.1,"output":0.1},"limit":{"context":128000,"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},"status":"deprecated"},"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},"status":"deprecated"},"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},"status":"deprecated"},"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}},"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-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-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-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-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.20-multi-agent":{"id":"xai/grok-4.20-multi-agent","name":"Grok-4.20-Multi-Agent","attachment":true,"reasoning":false,"tool_call":true,"temperature":false,"release_date":"2026-03-13","last_updated":"2026-03-13","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6,"cache_read":0.2},"limit":{"context":128000,"output":0}},"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-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}},"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}},"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}},"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/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/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},"status":"deprecated"},"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/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/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/gpt-image-2":{"id":"openai/gpt-image-2","name":"GPT-Image-2","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"cost":{"input":5.0505,"output":32.3232,"cache_read":1.2626},"limit":{"context":0,"output":0}},"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},"status":"deprecated"},"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-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-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-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-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/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/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-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/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.4-mini":{"id":"openai/gpt-5.4-mini","name":"GPT-5.4-Mini","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-12","last_updated":"2026-03-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.68,"output":4,"cache_read":0.068},"limit":{"context":400000,"input":272000,"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/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-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"GPT-5.4-Nano","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.18,"output":1.1,"cache_read":0.018},"limit":{"context":400000,"input":272000,"output":128000}},"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/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/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.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/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/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.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/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/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/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/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-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-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.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.3-codex-spark":{"id":"openai/gpt-5.3-codex-spark","name":"GPT-5.3-Codex-Spark","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-04","last_updated":"2026-03-04","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":128000,"output":16384}},"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-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/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-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/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-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":{"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/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/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-image-1.5":{"id":"openai/gpt-image-1.5","name":"gpt-image-1.5","attachment":true,"reasoning":false,"tool_call":false,"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-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-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-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}},"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-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},"status":"deprecated"},"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/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}},"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/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-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/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/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/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},"status":"deprecated"},"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/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/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/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/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-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/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/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/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/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/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},"status":"deprecated"},"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}},"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/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/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/gemma-4-31b":{"id":"google/gemma-4-31b","name":"Gemma-4-31B","attachment":true,"reasoning":false,"tool_call":true,"temperature":false,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":8192}},"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/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}},"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}},"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},"status":"deprecated"},"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-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-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Claude-Opus-4.7","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-15","last_updated":"2026-04-15","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":4.3,"output":21,"cache_read":0.43,"cache_write":5.4},"limit":{"context":1048576,"output":128000}},"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.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-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}},"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-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-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-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},"status":"deprecated"},"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}},"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-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-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}},"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}},"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.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"cost":{"input":4.5455,"output":27.2727,"cache_read":0.4545},"limit":{"context":400000,"output":128000}},"openai/gpt-5.5-pro":{"id":"openai/gpt-5.5-pro","name":"GPT-5.5-Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"cost":{"input":27.2727,"output":163.6364},"limit":{"context":400000,"output":128000}}}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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":{"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}}}},"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":{"minimax-m2.7":{"id":"minimax-m2.7","name":"minimax-m2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":196608,"output":196608}},"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}},"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}},"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}},"gemma4:31b":{"id":"gemma4:31b","name":"gemma4:31b","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"output":262144}},"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}},"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":65536}},"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}},"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-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}},"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}},"gemini-3-flash-preview":{"id":"gemini-3-flash-preview","name":"gemini-3-flash-preview","family":"gemini-flash","attachment":true,"reasoning":true,"tool_call":true,"knowledge":"2025-01","release_date":"2025-12-17","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":65536}},"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}},"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}},"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}},"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}},"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}},"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}},"glm-5.1":{"id":"glm-5.1","name":"glm-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"release_date":"2026-03-27","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":202752,"output":131072}},"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}},"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}},"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}},"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}},"kimi-k2.6:cloud":{"id":"kimi-k2.6:cloud","name":"kimi-k2.6:cloud","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"output":262144}},"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}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"deepseek-v4-flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":1048576}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"deepseek-v4-pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":1048576}},"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}},"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}},"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}},"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}},"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}}}},"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-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-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"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-5-turbo":{"id":"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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":200000,"output":131072}},"glm-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":200000,"output":131072}}}},"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":{"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,"structured_output":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}},"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}},"nvidia.nemotron-super-3-120b":{"id":"nvidia.nemotron-super-3-120b","name":"NVIDIA Nemotron 3 Super 120B A12B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.65},"limit":{"context":262144,"output":131072}},"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}},"mistral.ministral-3-8b-instruct":{"id":"mistral.ministral-3-8b-instruct","name":"Ministral 3 8B","family":"ministral","attachment":false,"reasoning":false,"tool_call":true,"structured_output":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}},"au.anthropic.claude-opus-4-6-v1":{"id":"au.anthropic.claude-opus-4-6-v1","name":"AU Anthropic Claude Opus 4.6","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":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":16.5,"output":82.5,"cache_read":1.65,"cache_write":20.625},"limit":{"context":1000000,"output":128000}},"mistral.ministral-3-3b-instruct":{"id":"mistral.ministral-3-3b-instruct","name":"Ministral 3 3B","family":"ministral","attachment":false,"reasoning":false,"tool_call":true,"structured_output":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}},"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,"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},"limit":{"context":200000,"output":64000}},"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}},"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,"structured_output":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.voxtral-small-24b-2507":{"id":"mistral.voxtral-small-24b-2507","name":"Voxtral Small 24B 2507","family":"mistral","attachment":true,"reasoning":false,"tool_call":true,"structured_output":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}},"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,"structured_output":true,"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}},"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}},"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,"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}},"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}},"global.anthropic.claude-opus-4-7":{"id":"global.anthropic.claude-opus-4-7","name":"Claude Opus 4.7 (Global)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"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}},"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}},"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,"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},"limit":{"context":200000,"output":64000}},"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,"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}},"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}},"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,"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},"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,"structured_output":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}},"zai.glm-5":{"id":"zai.glm-5","name":"GLM-5","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3.2},"limit":{"context":202752,"output":101376}},"qwen.qwen3-32b-v1:0":{"id":"qwen.qwen3-32b-v1:0","name":"Qwen3 32B (dense)","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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}},"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}},"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,"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}},"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}},"us.anthropic.claude-opus-4-7":{"id":"us.anthropic.claude-opus-4-7","name":"Claude Opus 4.7 (US)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"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}},"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,"structured_output":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}},"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,"structured_output":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}},"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}},"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}},"anthropic.claude-opus-4-7":{"id":"anthropic.claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"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-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}},"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,"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}},"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}},"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-v1:0":{"id":"deepseek.v3-v1:0","name":"DeepSeek-V3.1","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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.magistral-small-2509":{"id":"mistral.magistral-small-2509","name":"Magistral Small 1.2","family":"magistral","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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}},"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}},"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,"structured_output":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}},"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,"structured_output":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-14b-instruct":{"id":"mistral.ministral-3-14b-instruct","name":"Ministral 14B 3.0","family":"ministral","attachment":false,"reasoning":false,"tool_call":true,"structured_output":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}},"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}},"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,"structured_output":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}},"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,"structured_output":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}},"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,"structured_output":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}},"eu.anthropic.claude-opus-4-7":{"id":"eu.anthropic.claude-opus-4-7","name":"Claude Opus 4.7 (EU)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"minimax.minimax-m2.5":{"id":"minimax.minimax-m2.5","name":"MiniMax M2.5","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2},"limit":{"context":196608,"output":98304}},"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}},"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}},"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,"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},"limit":{"context":200000,"output":64000}},"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,"structured_output":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}},"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}},"au.anthropic.claude-sonnet-4-6":{"id":"au.anthropic.claude-sonnet-4-6","name":"AU Anthropic Claude Sonnet 4.6","family":"claude-sonnet","attachment":true,"reasoning":true,"tool_call":true,"structured_output":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.3,"output":16.5,"cache_read":0.33,"cache_write":4.125},"limit":{"context":1000000,"output":128000}},"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,"structured_output":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}},"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,"structured_output":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}},"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}},"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}},"qwen.qwen3-coder-next":{"id":"qwen.qwen3-coder-next","name":"Qwen3 Coder Next","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-06","last_updated":"2026-02-06","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.22,"output":1.8},"limit":{"context":131072,"output":65536}},"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,"structured_output":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}},"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,"structured_output":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}},"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,"structured_output":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}},"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}},"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}},"us.deepseek.r1-v1:0":{"id":"us.deepseek.r1-v1:0","name":"DeepSeek-R1 (US)","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}},"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,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","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}},"us.meta.llama4-maverick-17b-instruct-v1:0":{"id":"us.meta.llama4-maverick-17b-instruct-v1:0","name":"Llama 4 Maverick 17B Instruct (US)","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}},"au.anthropic.claude-haiku-4-5-20251001-v1:0":{"id":"au.anthropic.claude-haiku-4-5-20251001-v1:0","name":"Claude Haiku 4.5 (AU)","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}},"jp.anthropic.claude-sonnet-4-5-20250929-v1:0":{"id":"jp.anthropic.claude-sonnet-4-5-20250929-v1:0","name":"Claude Sonnet 4.5 (JP)","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-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-31","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}},"jp.anthropic.claude-sonnet-4-6":{"id":"jp.anthropic.claude-sonnet-4-6","name":"Claude Sonnet 4.6 (JP)","family":"claude-sonnet","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","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}},"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-31","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}},"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-31","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}},"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,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","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}},"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,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","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}},"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}},"au.anthropic.claude-sonnet-4-5-20250929-v1:0":{"id":"au.anthropic.claude-sonnet-4-5-20250929-v1:0","name":"Claude Sonnet 4.5 (AU)","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}},"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-31","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}},"us.meta.llama4-scout-17b-instruct-v1:0":{"id":"us.meta.llama4-scout-17b-instruct-v1:0","name":"Llama 4 Scout 17B Instruct (US)","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}},"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,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","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}},"jp.anthropic.claude-opus-4-7":{"id":"jp.anthropic.claude-opus-4-7","name":"Claude Opus 4.7 (JP)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}}}},"the-grid-ai":{"id":"the-grid-ai","env":["THEGRIDAI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.thegrid.ai/v1","name":"The Grid AI","doc":"https://thegrid.ai/docs","models":{"text-prime":{"id":"text-prime","name":"Text Prime","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":128000,"output":30000},"status":"beta"},"text-standard":{"id":"text-standard","name":"Text Standard","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":128000,"output":16000},"status":"beta"},"text-max":{"id":"text-max","name":"Text Max","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-24","last_updated":"2026-03-24","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":1000000,"output":128000},"status":"beta"}}},"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.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}},"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}},"nvidia/Nemotron-120B-A12B":{"id":"nvidia/Nemotron-120B-A12B","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}},"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-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}},"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"},"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}},"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"},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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}},"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-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.15},"limit":{"context":1000000,"output":384000}}}},"frogbot":{"id":"frogbot","env":["FROGBOT_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://app.frogbot.ai/api/v1","name":"FrogBot","doc":"https://docs.frogbot.ai","models":{"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-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}},"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}},"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-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":200000,"output":64000}},"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}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"zai-glm-5-1":{"id":"zai-glm-5-1","name":"Z.AI GLM-5.1","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":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":198000,"output":8192}},"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}},"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}},"gpt-5-4-nano":{"id":"gpt-5-4-nano","name":"GPT-5.4 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.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"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,"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-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}},"gpt-5-5":{"id":"gpt-5-5","name":"GPT-5.5","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}},"grok-4-3":{"id":"grok-4-3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2024-11","release_date":"2026-04-30","last_updated":"2026-04-30","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2},"limit":{"context":1000000,"output":128000}},"gpt-5-4-mini":{"id":"gpt-5-4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"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-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}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek v4 Pro","family":"deepseek","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2026-01","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.74,"output":3.48,"cache_read":0.14},"limit":{"context":128000,"output":8192}},"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}},"qwen-3-6-plus":{"id":"qwen-3-6-plus","name":"Qwen 3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.1},"limit":{"context":1000000,"output":64000}},"minimax-m2-7":{"id":"minimax-m2-7","name":"MiniMax-M2.7","family":"minimax","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-09","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":192000,"output":8192}},"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}},"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}},"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}},"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}},"kimi-k2-6":{"id":"kimi-k2-6","name":"Kimi-K2.6","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.95,"output":4,"cache_read":0.16},"limit":{"context":256000,"output":128000}},"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}}}},"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-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":200000,"output":131072}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":200000,"output":131072}},"glm-5-turbo":{"id":"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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"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.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}}}},"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":{"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}},"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}},"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}},"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.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","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-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}},"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}},"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}}}},"venice":{"id":"venice","env":["VENICE_API_KEY"],"npm":"venice-ai-sdk-provider","name":"Venice AI","doc":"https://docs.venice.ai","models":{"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}},"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}},"grok-4-20-multi-agent":{"id":"grok-4-20-multi-agent","name":"Grok 4.20 Multi-Agent","family":"grok","attachment":true,"reasoning":true,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2026-03-12","last_updated":"2026-05-07","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.42,"output":2.83,"cache_read":0.23,"context_over_200k":{"input":2.83,"output":5.67,"cache_read":0.45}},"limit":{"context":2000000,"output":128000}},"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}},"z-ai-glm-5v-turbo":{"id":"z-ai-glm-5v-turbo","name":"GLM 5V Turbo","family":"glmv","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.5,"output":5,"cache_read":0.3},"limit":{"context":200000,"output":32768}},"gemma-4-uncensored":{"id":"gemma-4-uncensored","name":"Gemma 4 Uncensored","family":"gemma","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-13","last_updated":"2026-04-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1625,"output":0.5},"limit":{"context":256000,"output":8192}},"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-04-09","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.23,"output":0.57,"cache_read":0.06},"limit":{"context":1000000,"output":30000}},"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,"knowledge":"2025-08-31","release_date":"2026-02-17","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3.6,"output":18,"cache_read":0.36,"cache_write":4.5},"limit":{"context":1000000,"output":64000}},"nvidia-nemotron-cascade-2-30b-a3b":{"id":"nvidia-nemotron-cascade-2-30b-a3b","name":"Nemotron Cascade 2 30B A3B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-24","last_updated":"2026-04-09","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.8},"limit":{"context":256000,"output":32768}},"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":{"id":"grok-4-20","name":"Grok 4.20","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-12","last_updated":"2026-05-07","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.42,"output":2.83,"cache_read":0.23,"context_over_200k":{"input":2.83,"output":5.67,"cache_read":0.45}},"limit":{"context":2000000,"output":128000}},"google-gemma-4-26b-a4b-it":{"id":"google-gemma-4-26b-a4b-it","name":"Google Gemma 4 26B A4B Instruct","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-12","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.1625,"output":0.5},"limit":{"context":256000,"output":8192}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"release_date":"2026-04-16","last_updated":"2026-04-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":30,"cache_read":0.6,"cache_write":7.5},"limit":{"context":1000000,"output":128000}},"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}},"qwen3-5-397b-a17b":{"id":"qwen3-5-397b-a17b","name":"Qwen 3.5 397B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-16","last_updated":"2026-04-16","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.75,"output":4.5},"limit":{"context":128000,"output":32768}},"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}},"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}},"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}},"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}},"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}},"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}},"venice-uncensored-1-2":{"id":"venice-uncensored-1-2","name":"Venice Uncensored 1.2","family":"venice","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.9},"limit":{"context":128000,"output":8192}},"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}},"mistral-small-3-2-24b-instruct":{"id":"mistral-small-3-2-24b-instruct","name":"Mistral Small 3.2 24B Instruct","family":"mistral-small","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-01-15","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.09375,"output":0.25},"limit":{"context":256000,"output":16384}},"minimax-m27":{"id":"minimax-m27","name":"MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.375,"output":1.5,"cache_read":0.075},"limit":{"context":198000,"output":32768}},"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}},"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-04-16","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}},"mercury-2":{"id":"mercury-2","name":"Mercury 2","family":"mercury","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-20","last_updated":"2026-04-09","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3125,"output":0.9375,"cache_read":0.03125},"limit":{"context":128000,"output":50000}},"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}},"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}},"openai-gpt-55-pro":{"id":"openai-gpt-55-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":37.5,"output":225},"limit":{"context":1000000,"output":128000}},"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}},"venice-uncensored-role-play":{"id":"venice-uncensored-role-play","name":"Venice Role Play Uncensored","family":"venice","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-20","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":2},"limit":{"context":128000,"output":4096}},"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.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":"2024-04-01","last_updated":"2026-04-04","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.85,"output":2.75,"cache_read":0.3},"limit":{"context":198000,"output":16384}},"grok-4-3":{"id":"grok-4-3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-18","last_updated":"2026-05-04","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.42,"output":2.83,"cache_read":0.23,"context_over_200k":{"input":2.83,"output":5.67,"cache_read":0.45}},"limit":{"context":1000000,"output":32000}},"mistral-small-2603":{"id":"mistral-small-2603","name":"Mistral Small 4","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-04-09","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1875,"output":0.75},"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":true,"tool_call":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-11-06","last_updated":"2026-05-06","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.07,"output":0.3},"limit":{"context":128000,"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,"structured_output":true,"temperature":true,"release_date":"2025-12-06","last_updated":"2026-04-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":30,"cache_read":0.6,"cache_write":7.5},"limit":{"context":198000,"output":32768}},"qwen3-5-9b":{"id":"qwen3-5-9b","name":"Qwen 3.5 9B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-05","last_updated":"2026-04-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.15},"limit":{"context":256000,"output":32768}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-29","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.17,"output":0.35,"cache_read":0.028},"limit":{"context":1000000,"output":32768}},"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}},"openai-gpt-54-mini":{"id":"openai-gpt-54-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-31","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.9375,"output":5.625,"cache_read":0.09375},"limit":{"context":400000,"output":128000}},"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-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.34,"output":1.19,"cache_read":0.04},"limit":{"context":198000,"output":32768}},"zai-org-glm-5-1":{"id":"zai-org-glm-5-1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.75,"output":5.5,"cache_read":0.325},"limit":{"context":200000,"output":24000}},"openai-gpt-55":{"id":"openai-gpt-55","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-04-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":6.25,"output":37.5,"cache_read":0.625,"context_over_200k":{"input":12.5,"output":56.25,"cache_read":1.25}},"limit":{"context":1000000,"output":131072}},"qwen3-6-27b":{"id":"qwen3-6-27b","name":"Qwen 3.6 27B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-29","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.325,"output":3.25},"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,"knowledge":"2025-05-31","release_date":"2026-02-05","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":30,"cache_read":0.6,"cache_write":7.5},"limit":{"context":1000000,"output":128000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-29","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.73,"output":3.796,"cache_read":0.33},"limit":{"context":1000000,"output":32768}},"deepseek-v3.2":{"id":"deepseek-v3.2","name":"DeepSeek V3.2","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-10","release_date":"2025-12-04","last_updated":"2026-03-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.33,"output":0.48,"cache_read":0.16},"limit":{"context":160000,"output":32768}},"qwen-3-6-plus":{"id":"qwen-3-6-plus","name":"Qwen 3.6 Plus Uncensored","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-06","last_updated":"2026-04-12","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.625,"output":3.75,"cache_read":0.0625,"cache_write":0.78,"context_over_200k":{"input":2.5,"output":7.5,"cache_read":0.0625,"cache_write":0.78}},"limit":{"context":1000000,"output":65536}},"aion-labs-aion-2-0":{"id":"aion-labs-aion-2-0","name":"Aion 2.0","family":"o","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-24","last_updated":"2026-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":2,"cache_read":0.25},"limit":{"context":128000,"output":32768}},"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,"release_date":"2025-01-15","last_updated":"2026-04-12","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":64000}},"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}},"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}},"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-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.56,"output":3.5,"cache_read":0.22},"limit":{"context":256000,"output":65536}},"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}},"arcee-trinity-large-thinking":{"id":"arcee-trinity-large-thinking","name":"Trinity Large Thinking","family":"trinity","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-04","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3125,"output":1.125,"cache_read":0.075},"limit":{"context":256000,"output":65536}},"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}},"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}},"kimi-k2-6":{"id":"kimi-k2-6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.85,"output":4.655,"cache_read":0.22},"limit":{"context":256000,"output":65536}},"claude-opus-4-6-fast":{"id":"claude-opus-4-6-fast","name":"Claude Opus 4.6 Fast","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":36,"output":180,"cache_read":3.6,"cache_write":45},"limit":{"context":1000000,"output":128000}},"z-ai-glm-5-turbo":{"id":"z-ai-glm-5-turbo","name":"GLM 5 Turbo","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-15","last_updated":"2026-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":200000,"output":32768}},"google-gemma-4-31b-it":{"id":"google-gemma-4-31b-it","name":"Google Gemma 4 31B Instruct","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-03","last_updated":"2026-04-12","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.175,"output":0.5},"limit":{"context":256000,"output":8192}}}},"aihubmix":{"id":"aihubmix","env":["AIHUBMIX_API_KEY"],"npm":"@aihubmix/ai-sdk-provider","name":"AIHubMix","doc":"https://docs.aihubmix.com","models":{"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2958,"output":1.1832,"cache_read":0.05916},"limit":{"context":200000,"output":128000}},"coding-glm-5.1-free":{"id":"coding-glm-5.1-free","name":"Coding GLM 5.1 (free)","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-11","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":204800,"output":128000}},"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","audio","video","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}},"kimi-k2.5":{"id":"kimi-k2.5","name":"Kimi K2.5","family":"kimi-k2.5","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.105},"limit":{"context":256000,"output":0}},"glm-5v-turbo":{"id":"glm-5v-turbo","name":"GLM 5 Vision Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-05-09","last_updated":"2026-05-09","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.7042,"output":3.09848,"cache_read":0.169008},"limit":{"context":200000,"output":128000}},"grok-4.3":{"id":"grok-4.3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2,"context_over_200k":{"input":2.5,"output":5,"cache_read":0.4}},"limit":{"context":1000000,"output":1000000}},"coding-minimax-m2.7-highspeed":{"id":"coding-minimax-m2.7-highspeed","name":"Coding MiniMax M2.7 Highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":204800,"output":13100}},"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-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":200000,"output":64000}},"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","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":12,"cache_read":0.2},"limit":{"context":1048576,"output":65536}},"coding-glm-5.1":{"id":"coding-glm-5.1","name":"Coding-GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-11","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.06,"output":0.22},"limit":{"context":200000,"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","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05},"limit":{"context":1048576,"output":65536}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":1050000,"output":128000}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"deepseek-v4-flash-think":{"id":"deepseek-v4-flash-think","name":"DeepSeek V4 Flash Think","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.154,"output":0.308,"cache_read":0.0308},"limit":{"context":1000000,"output":384000}},"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}},"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.125},"limit":{"context":1048576,"output":65536}},"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}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-05-09","last_updated":"2026-05-09","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.282,"output":1.692,"cache_read":0.0282,"cache_write":0.3525},"limit":{"context":991000,"output":64000}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4-Mini","family":"gpt-mini","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":false,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"output":128000}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.845,"output":3.38,"cache_read":0.183112},"limit":{"context":200000,"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.275},"limit":{"context":200000,"output":100000}},"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,"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-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.499,"cache_read":0.03},"limit":{"context":1048576,"output":65536}},"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}},"gemini-3.1-flash-lite":{"id":"gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","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","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1.5,"cache_read":0.25},"limit":{"context":1048576,"output":65536}},"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}},"claude-opus-4-6-think":{"id":"claude-opus-4-6-think","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-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":200000,"output":32000}},"coding-minimax-m2.7-free":{"id":"coding-minimax-m2.7-free","name":"Coding-MiniMax-M2.7-Free","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":204800,"output":13100}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.154,"output":0.308,"cache_read":0.0308},"limit":{"context":1000000,"output":384000}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":3.9995,"cache_read":0.160835},"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,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":15,"cache_read":0.25},"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-31","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}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.478,"output":0.956,"cache_read":0.004302},"limit":{"context":1000000,"output":384000}},"claude-opus-4-7-think":{"id":"claude-opus-4-7-think","name":"Claude Opus 4.7 Thinking","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"coding-minimax-m2.7":{"id":"coding-minimax-m2.7","name":"Coding MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":204800,"output":13100}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-05-09","last_updated":"2026-05-09","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.268,"output":7.608,"cache_read":0.1268,"cache_write":1.585},"limit":{"context":240000,"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":2,"output":8,"cache_read":0.5},"limit":{"context":1047576,"output":32768}},"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}},"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}},"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-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":200000,"output":64000}},"qwen3.6-flash":{"id":"qwen3.6-flash","name":"Qwen3.6 Flash","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.169,"output":1.014,"cache_read":0.0169,"cache_write":0.21125},"limit":{"context":991000,"output":64000}}}},"cerebras":{"id":"cerebras","env":["CEREBRAS_API_KEY"],"npm":"@ai-sdk/cerebras","name":"Cerebras","doc":"https://inference-docs.cerebras.ai/models/overview","models":{"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}},"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}},"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}},"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}}}},"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":{"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}},"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}},"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}}}},"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":{"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}},"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}}}},"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-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-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-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.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"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.5":{"id":"kimi-k2.5","name":"Kimi K2.5","family":"kimi-k2.5","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}}}},"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-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-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-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"}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":false,"reasoning":true,"tool_call":true,"interleaved":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4},"limit":{"context":262144,"output":262144},"provider":{"npm":"@ai-sdk/openai-compatible","api":"https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/models","shape":"completions"}},"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,"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-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"}},"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}},"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}},"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-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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"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}},"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":{"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}},"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":{"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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.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-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}},"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-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.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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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-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-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}},"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}},"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}},"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-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}},"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}},"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}},"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"}},"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}},"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}},"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}},"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}},"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}},"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}},"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.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}},"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-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000}},"gpt-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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}},"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-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-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}},"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}},"cohere-command-r-plus-08-2024":{"id":"cohere-command-r-plus-08-2024","name":"Command R+","family":"command-r","attachment":false,"reasoning":false,"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-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}},"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}},"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}},"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.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}},"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}},"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-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}},"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}},"cohere-command-r-08-2024":{"id":"cohere-command-r-08-2024","name":"Command R","family":"command-r","attachment":false,"reasoning":false,"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-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}},"gpt-4-turbo":{"id":"gpt-4-turbo","name":"GPT-4 Turbo","family":"gpt","attachment":true,"reasoning":false,"tool_call":true,"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.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000}}}},"abliteration-ai":{"id":"abliteration-ai","env":["ABLIT_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.abliteration.ai/v1","name":"abliteration.ai","doc":"https://docs.abliteration.ai/models","models":{"abliterated-model":{"id":"abliterated-model","name":"Abliterated Model","attachment":true,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2026-01-06","last_updated":"2026-01-06","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":3,"output":3},"limit":{"context":150000,"input":150000,"output":8192}}}},"wafer.ai":{"id":"wafer.ai","env":["WAFER_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://pass.wafer.ai/v1","name":"Wafer","doc":"https://docs.wafer.ai/wafer-pass","models":{"Qwen3.5-397B-A17B":{"id":"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,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":262144,"output":65536}},"GLM-5.1":{"id":"GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":202752,"output":131072}},"MiniMax-M2.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":204800,"output":131072}},"DeepSeek-V4-Pro":{"id":"DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":1000000,"output":384000}}}},"cohere":{"id":"cohere","env":["COHERE_API_KEY"],"npm":"@ai-sdk/cohere","name":"Cohere","doc":"https://docs.cohere.com/docs/models","models":{"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}},"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}},"c4ai-aya-vision-8b":{"id":"c4ai-aya-vision-8b","name":"Aya Vision 8B","attachment":true,"reasoning":false,"tool_call":false,"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-r-plus-08-2024":{"id":"command-r-plus-08-2024","name":"Command R+","family":"command-r","attachment":false,"reasoning":false,"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}},"c4ai-aya-expanse-8b":{"id":"c4ai-aya-expanse-8b","name":"Aya Expanse 8B","attachment":false,"reasoning":false,"tool_call":false,"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}},"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-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}},"c4ai-aya-vision-32b":{"id":"c4ai-aya-vision-32b","name":"Aya Vision 32B","attachment":true,"reasoning":false,"tool_call":false,"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-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":false,"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}},"c4ai-aya-expanse-32b":{"id":"c4ai-aya-expanse-32b","name":"Aya Expanse 32B","attachment":false,"reasoning":false,"tool_call":false,"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":false,"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}}}},"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":{"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}},"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}},"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}},"MiniMaxAI/MiniMax-M2.5":{"id":"MiniMaxAI/MiniMax-M2.5","name":"MiniMax-M2.5","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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,"input":180000,"output":16000}}}},"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}}}},"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-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-4.3":{"id":"grok-4.3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2,"context_over_200k":{"input":2.5,"output":5,"cache_read":0.4}},"limit":{"context":1000000,"output":30000}},"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-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-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-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-4.20-0309-reasoning":{"id":"grok-4.20-0309-reasoning","name":"Grok 4.20 (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}},"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-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-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-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-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-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-4.20-0309-non-reasoning":{"id":"grok-4.20-0309-non-reasoning","name":"Grok 4.20 (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}},"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-4.20-multi-agent-0309":{"id":"grok-4.20-multi-agent-0309","name":"Grok 4.20 Multi-Agent","family":"grok","attachment":true,"reasoning":true,"tool_call":false,"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}},"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-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-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-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-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-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-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-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-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}}}},"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":{"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/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/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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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.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}},"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}},"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}},"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}}}},"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-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-31","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,"context_over_200k":{"input":6,"output":22.5,"cache_read":0.6,"cache_write":7.5}},"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-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-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-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-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-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-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-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-31","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,"context_over_200k":{"input":10,"output":37.5,"cache_read":1,"cache_write":12.5}},"limit":{"context":1000000,"output":128000}},"claude-opus-4-7@default":{"id":"claude-opus-4-7@default","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}}}},"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":{"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}},"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-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}},"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/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/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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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: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-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.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-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.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-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: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.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.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:Qwen/Qwen3.5-397B-A17B":{"id":"hf:Qwen/Qwen3.5-397B-A17B","name":"Qwen3.5-97B-A17B","family":"qwen","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-11","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3,"cache_read":0.6},"limit":{"context":262144,"output":65536},"status":"beta"},"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-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}},"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/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: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-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-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.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.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: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: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: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:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4":{"id":"hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4","name":"Nemotron 3 Super 120B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-03-11","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1,"cache_read":0.3},"limit":{"context":262144,"output":65536}},"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: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.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:zai-org/GLM-5.1":{"id":"hf:zai-org/GLM-5.1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":1},"limit":{"context":196608,"output":65536}},"hf:zai-org/GLM-5":{"id":"hf: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-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":1},"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:moonshotai/Kimi-K2.6":{"id":"hf:moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.95},"limit":{"context":262144,"output":65536}}}},"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":{"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}},"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}},"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-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/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}},"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/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/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}},"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.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-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-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}},"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-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-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}},"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning":{"id":"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning","name":"Nemotron 3 Nano Omni","family":"nemotron","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-28","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":256000,"output":65536}},"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/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/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/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/nemotron-3-super-120b-a12b":{"id":"nvidia/nemotron-3-super-120b-a12b","name":"Nemotron 3 Super","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.8},"limit":{"context":262144,"output":262144}},"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/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}},"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/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/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/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.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/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/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}},"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":{"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-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}},"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-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}},"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}},"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}},"minimaxai/minimax-m2.7":{"id":"minimaxai/minimax-m2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"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,"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}},"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}},"z-ai/glm-5.1":{"id":"z-ai/glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":131072}},"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.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/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/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/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-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/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.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-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}},"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}},"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/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/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.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/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/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/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}},"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}},"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-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-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/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-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Gemma-4-31B-IT","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":256000,"output":16384}},"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/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-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/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-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}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"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-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}},"mistralai/mistral-medium-3.5-128b":{"id":"mistralai/mistral-medium-3.5-128b","name":"Mistral Medium 3.5 128B","family":"mistral-medium","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-29","last_updated":"2026-04-29","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"deepseek-ai/deepseek-v4-flash":{"id":"deepseek-ai/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1048576,"output":393216}},"deepseek-ai/deepseek-v4-pro":{"id":"deepseek-ai/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1048576,"output":393216}}}},"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}},"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-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.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.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}},"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}},"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}},"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}},"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}}}},"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-edit-2":{"id":"mercury-edit-2","name":"Mercury Edit 2","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","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-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}}}},"openai":{"id":"openai","env":["OPENAI_API_KEY"],"npm":"@ai-sdk/openai","name":"OpenAI","doc":"https://platform.openai.com/docs/models","models":{"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}},"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-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}},"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-5.3-chat-latest":{"id":"gpt-5.3-chat-latest","name":"GPT-5.3 Chat (latest)","family":"gpt","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"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}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"experimental":{"modes":{"fast":{"cost":{"input":12.5,"output":75,"cache_read":1.25},"provider":{"body":{"service_tier":"priority"}}}}}},"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-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}},"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-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}},"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.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}},"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-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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6,"cache_read":0.08},"limit":{"context":128000,"output":16384}},"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.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000},"experimental":{"modes":{"fast":{"cost":{"input":1.5,"output":9,"cache_read":0.15},"provider":{"body":{"service_tier":"priority"}}}}}},"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-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000}},"gpt-image-1":{"id":"gpt-image-1","name":"gpt-image-1","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-04-24","last_updated":"2025-04-24","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"limit":{"context":0,"input":0,"output":0}},"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}},"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.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}},"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-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.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}},"gpt-image-1-mini":{"id":"gpt-image-1-mini","name":"gpt-image-1-mini","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-09-26","last_updated":"2025-09-26","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"limit":{"context":0,"input":0,"output":0}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":60,"cache_read":7.5},"limit":{"context":200000,"output":100000}},"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}},"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}},"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}},"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}},"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}},"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-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-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.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},"experimental":{"modes":{"fast":{"cost":{"input":5,"output":30,"cache_read":0.5},"provider":{"body":{"service_tier":"priority"}}}}}},"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-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}},"chatgpt-image-latest":{"id":"chatgpt-image-latest","name":"chatgpt-image-latest","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-12-16","last_updated":"2025-12-16","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"limit":{"context":0,"input":0,"output":0}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":200000,"output":100000}},"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":{"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10,"cache_read":1.25},"limit":{"context":128000,"output":16384}},"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}},"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}},"gpt-image-1.5":{"id":"gpt-image-1.5","name":"gpt-image-1.5","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-11-25","last_updated":"2025-11-25","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"limit":{"context":0,"input":0,"output":0}},"gpt-5.5-pro":{"id":"gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":1047576,"output":32768}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":1.6,"cache_read":0.1},"limit":{"context":1047576,"output":32768}},"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-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}}}},"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":{"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}},"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-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.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}},"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-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-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/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-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-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-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":{"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.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-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":{"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/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.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}},"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-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}},"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-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,"knowledge":"2025-08-31","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-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-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-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-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-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}},"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.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-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-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-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}},"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.31,"cache_write":2.375,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"limit":{"context":1048576,"output":65536}},"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}},"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}}}},"digitalocean":{"id":"digitalocean","env":["DIGITALOCEAN_ACCESS_TOKEN"],"npm":"@ai-sdk/openai-compatible","api":"https://inference.do-ai.run/v1","name":"DigitalOcean","doc":"https://docs.digitalocean.com/products/gradient-ai-platform/details/models/","models":{"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6,"cache_read":0.075},"limit":{"context":128000,"output":16384}},"multi-qa-mpnet-base-dot-v1":{"id":"multi-qa-mpnet-base-dot-v1","name":"Multi-QA-mpnet-base-dot-v1","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2021-08-30","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.009,"output":0},"limit":{"context":512,"output":768}},"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-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":2.7},"limit":{"context":262144,"output":32768}},"nemotron-3-nano-omni":{"id":"nemotron-3-nano-omni","name":"Nemotron Nano 3 Omni","family":"nemotron","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-30","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":0.9},"limit":{"context":65536,"output":65536}},"llama3-8b-instruct":{"id":"llama3-8b-instruct","name":"Llama 3.1 Instruct (8B)","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.198,"output":0.198},"limit":{"context":131072,"output":131072}},"anthropic-claude-opus-4.7":{"id":"anthropic-claude-opus-4.7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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":{"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.3,"cache_write":3.75}},"limit":{"context":1000000,"output":64000}},"wan2-2-t2v-a14b":{"id":"wan2-2-t2v-a14b","name":"Wan2.2-T2V-A14B","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-07-28","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["video"]},"open_weights":true,"cost":{"input":0.6,"output":0},"limit":{"context":100,"output":1}},"qwen-2.5-14b-instruct":{"id":"qwen-2.5-14b-instruct","name":"Qwen 2.5 14B Instruct","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-09","release_date":"2024-09-19","last_updated":"2024-09-19","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":131072,"output":131072}},"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":1000000,"output":128000}},"qwen3.5-397b-a17b":{"id":"qwen3.5-397b-a17b","name":"Qwen 3.5 397B A17B","family":"qwen3.5","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-15","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.55,"output":3.5},"limit":{"context":262144,"output":81920}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":200000,"output":100000}},"e5-large-v2":{"id":"e5-large-v2","name":"E5 Large v2","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2023-05-19","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.02,"output":0},"limit":{"context":512,"output":1024}},"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,"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}},"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-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3.2},"limit":{"context":202752,"output":128000}},"openai-gpt-5.4-nano":{"id":"openai-gpt-5.4-nano","name":"GPT-5.4 nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"output":128000}},"mistral-7b-instruct-v0.3":{"id":"mistral-7b-instruct-v0.3","name":"Mistral 7B Instruct v0.3","family":"mistral","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2024-05-22","last_updated":"2024-05-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":32768,"output":32768}},"llama3.3-70b-instruct":{"id":"llama3.3-70b-instruct","name":"Llama 3.3 Instruct 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.65,"output":0.65},"limit":{"context":128000,"output":128000}},"mistral-3-14B":{"id":"mistral-3-14B","name":"Ministral 3 14B Instruct","family":"ministral","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-15","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":262144,"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,"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.99,"output":0.99},"limit":{"context":131072,"output":32768}},"alibaba-qwen3-32b":{"id":"alibaba-qwen3-32b","name":"Qwen3-32B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-30","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":0.55},"limit":{"context":131000,"output":40960}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":60,"cache_read":7.5},"limit":{"context":200000,"output":100000}},"anthropic-claude-3-opus":{"id":"anthropic-claude-3-opus","name":"Claude 3 Opus","family":"claude-opus","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2023-08","release_date":"2024-02-29","last_updated":"2024-02-29","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":4096},"status":"deprecated"},"stable-diffusion-3.5-large":{"id":"stable-diffusion-3.5-large","name":"Stable Diffusion 3.5 Large","family":"stable-diffusion","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-10-22","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["image"]},"open_weights":true,"cost":{"input":0.08,"output":0},"limit":{"context":256,"output":1}},"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,"output":128000}},"llama-4-maverick":{"id":"llama-4-maverick","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-05","last_updated":"2026-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":0.87},"limit":{"context":1000000,"output":16384}},"anthropic-claude-4.5-sonnet":{"id":"anthropic-claude-4.5-sonnet","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,"context_over_200k":{"input":6,"output":22.5,"cache_read":0.3,"cache_write":3.75}},"limit":{"context":1000000,"output":64000}},"qwen3-embedding-0.6b":{"id":"qwen3-embedding-0.6b","name":"Qwen3 Embedding 0.6B","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-06-03","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.04,"output":0},"limit":{"context":8000,"output":1024},"status":"beta"},"anthropic-claude-4.5-haiku":{"id":"anthropic-claude-4.5-haiku","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":1,"output":5,"cache_read":1,"cache_write":1.25},"limit":{"context":200000,"output":64000}},"gte-large-en-v1.5":{"id":"gte-large-en-v1.5","name":"GTE Large (v1.5)","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-03-27","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.09,"output":0},"limit":{"context":8192,"output":1024}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":1047576,"output":32768}},"anthropic-claude-3.5-haiku":{"id":"anthropic-claude-3.5-haiku","name":"Claude 3.5 Haiku","family":"claude-haiku","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-07","release_date":"2024-11-05","last_updated":"2024-11-05","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":4,"cache_read":0.08,"cache_write":1},"limit":{"context":200000,"output":8192},"status":"deprecated"},"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}},"deepseek-3.2":{"id":"deepseek-3.2","name":"DeepSeek V3.2","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2025-12-02","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":1.6},"limit":{"context":128000,"output":64000}},"nemotron-3-nano-30b":{"id":"nemotron-3-nano-30b","name":"Nemotron 3 Nano 30B A3B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-04-14","last_updated":"2025-04-14","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"output":262144}},"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}},"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,"knowledge":"2024-06","release_date":"2025-08-05","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.05,"output":0.45},"limit":{"context":131072,"output":131072}},"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":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.45,"output":1.7},"limit":{"context":262144,"output":65536}},"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-oss-120b":{"id":"openai-gpt-oss-120b","name":"gpt-oss-120b","family":"gpt-oss","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-06","release_date":"2025-08-05","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.7},"limit":{"context":131072,"output":131072}},"gemma-4-31B-it":{"id":"gemma-4-31B-it","name":"Gemma 4 31B","family":"gemma","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.18,"output":0.5},"limit":{"context":256000,"output":8192}},"nemotron-nano-12b-v2-vl":{"id":"nemotron-nano-12b-v2-vl","name":"Nemotron Nano 12B v2 VL","family":"nemotron","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-10","release_date":"2025-12-01","last_updated":"2026-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.6},"limit":{"context":128000,"output":16384}},"anthropic-claude-4.1-opus":{"id":"anthropic-claude-4.1-opus","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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":false,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4},"limit":{"context":262144,"output":262144}},"openai-gpt-image-2":{"id":"openai-gpt-image-2","name":"GPT Image 2","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-04-24","last_updated":"2025-04-24","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"limit":{"context":0,"output":0}},"anthropic-claude-4.6-sonnet":{"id":"anthropic-claude-4.6-sonnet","name":"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-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,"context_over_200k":{"input":6,"output":22.5,"cache_read":0.3,"cache_write":3.75}},"limit":{"context":1000000,"output":64000}},"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,"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":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":1,"cache_write":1.25},"limit":{"context":200000,"output":64000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48},"limit":{"context":1048576,"output":393216}},"ministral-3-8b-instruct-2512":{"id":"ministral-3-8b-instruct-2512","name":"Ministral 3 8B","family":"ministral","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-12-15","last_updated":"2025-12-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"output":262144}},"minimax-m2.5":{"id":"minimax-m2.5","name":"MiniMax M2.5","family":"minimax-m2.5","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08","release_date":"2026-02-12","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2},"limit":{"context":204800,"output":128000},"status":"beta"},"openai-gpt-image-1":{"id":"openai-gpt-image-1","name":"GPT Image 1","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-04-24","last_updated":"2025-04-24","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"cost":{"input":5,"output":40,"cache_read":1.25},"limit":{"context":0,"output":0}},"openai-gpt-5.5":{"id":"openai-gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-30","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1000000,"output":128000}},"nvidia-nemotron-3-super-120b":{"id":"nvidia-nemotron-3-super-120b","name":"Nemotron-3-Super-120B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2026-02","release_date":"2026-03-11","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.65},"limit":{"context":256000,"output":32768},"status":"beta"},"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,"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,"output":128000}},"all-mini-lm-l6-v2":{"id":"all-mini-lm-l6-v2","name":"All-MiniLM-L6-v2","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2021-08-30","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.009,"output":0},"limit":{"context":256,"output":384}},"bge-m3":{"id":"bge-m3","name":"BGE M3","family":"bge","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-01-30","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.02,"output":0},"limit":{"context":8192,"output":1024}},"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":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}},"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-31","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,"context_over_200k":{"input":10,"output":37.5,"cache_read":0.5,"cache_write":6.25}},"limit":{"context":1000000,"output":128000}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10,"cache_read":1.25},"limit":{"context":128000,"output":16384}},"openai-gpt-5.4-mini":{"id":"openai-gpt-5.4-mini","name":"GPT-5.4 mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"output":128000}},"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,"output":128000}},"arcee-trinity-large-thinking":{"id":"arcee-trinity-large-thinking","name":"Trinity Large Thinking","family":"trinity","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":0.9,"cache_read":0.06},"limit":{"context":256000,"output":128000},"status":"beta"},"mistral-nemo-instruct-2407":{"id":"mistral-nemo-instruct-2407","name":"Mistral Nemo Instruct","family":"mistral","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2024-07-18","last_updated":"2024-07-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.3},"limit":{"context":128000,"output":16384},"status":"deprecated"},"deepseek-v3":{"id":"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":"2025-03-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":163840,"output":131072}},"bge-reranker-v2-m3":{"id":"bge-reranker-v2-m3","name":"BGE Reranker v2 M3","family":"bge","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-03-12","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.01,"output":0},"limit":{"context":8192,"output":1}},"anthropic-claude-3.7-sonnet":{"id":"anthropic-claude-3.7-sonnet","name":"Claude 3.7 Sonnet","family":"claude-sonnet","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-11","release_date":"2025-02-24","last_updated":"2025-02-24","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15,"cache_read":0.3,"cache_write":3.75},"limit":{"context":200000,"output":64000},"status":"deprecated"},"qwen3-tts-voicedesign":{"id":"qwen3-tts-voicedesign","name":"Qwen3 TTS VoiceDesign","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-04-21","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["audio"]},"open_weights":true,"limit":{"context":32768,"output":1}},"openai-gpt-image-1.5":{"id":"openai-gpt-image-1.5","name":"GPT Image 1.5","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-11-25","last_updated":"2025-11-25","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"cost":{"input":5,"output":10,"cache_read":1},"limit":{"context":0,"output":0}},"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,"output":128000}},"anthropic-claude-3.5-sonnet":{"id":"anthropic-claude-3.5-sonnet","name":"Claude 3.5 Sonnet","family":"claude-sonnet","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2024-06-20","last_updated":"2024-10-22","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15,"cache_read":0.3,"cache_write":3.75},"limit":{"context":200000,"output":8192},"status":"deprecated"},"fal-ai/fast-sdxl":{"id":"fal-ai/fast-sdxl","name":"Fast SDXL","family":"stable-diffusion","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2023-07-26","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["image"]},"open_weights":true,"limit":{"context":0,"output":0}},"fal-ai/flux/schnell":{"id":"fal-ai/flux/schnell","name":"FLUX.1 [schnell]","family":"flux","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-08-01","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["image"]},"open_weights":true,"limit":{"context":0,"output":0}},"fal-ai/elevenlabs/tts/multilingual-v2":{"id":"fal-ai/elevenlabs/tts/multilingual-v2","name":"ElevenLabs Multilingual TTS v2","family":"elevenlabs","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2023-08-22","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["audio"]},"open_weights":false,"limit":{"context":0,"output":0}},"fal-ai/stable-audio-25/text-to-audio":{"id":"fal-ai/stable-audio-25/text-to-audio","name":"Stable Audio 2.5 (Text-to-Audio)","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-10-08","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["audio"]},"open_weights":false,"limit":{"context":0,"output":0}}}},"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":{"MiniMax-M2.5":{"id":"MiniMax-M2.5","name":"MiniMax M2.5","family":"minimax","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-09","release_date":"2025-02-11","last_updated":"2025-02-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2},"limit":{"context":194000,"output":4096}},"GLM-5-FP8":{"id":"GLM-5-FP8","name":"GLM 5 FP8","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-02-11","last_updated":"2026-02-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.85,"output":3.1},"limit":{"context":200000,"output":131072}},"DeepSeek-V3.2":{"id":"DeepSeek-V3.2","name":"DeepSeek V3.2","family":"deepseek","attachment":false,"reasoning":false,"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.55,"output":1.65},"limit":{"context":127000,"output":4096}},"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-06","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":129000,"output":4096}},"Kimi-K2.5":{"id":"Kimi-K2.5","name":"Kimi K2 Instruct","family":"kimi","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.55,"output":2.75},"limit":{"context":254000,"output":32768}}}},"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":{"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}},"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}},"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}},"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.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","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-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}},"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}},"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}}}},"mistral":{"id":"mistral","env":["MISTRAL_API_KEY"],"npm":"@ai-sdk/mistral","name":"Mistral","doc":"https://docs.mistral.ai/getting-started/models/","models":{"mistral-small-latest":{"id":"mistral-small-latest","name":"Mistral Small (latest)","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":256000,"output":256000}},"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}},"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}},"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-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}},"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}},"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-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}},"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}},"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-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}},"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}},"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}},"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}},"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-small-2603":{"id":"mistral-small-2603","name":"Mistral Small 4","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":256000,"output":256000}},"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}},"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-2604":{"id":"mistral-medium-2604","name":"Mistral Medium 3.5","family":"mistral-medium","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-29","last_updated":"2026-04-29","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":1.5,"output":7.5},"limit":{"context":262144,"output":262144}},"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}},"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}},"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}},"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}},"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}},"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}},"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-medium-latest":{"id":"mistral-medium-latest","name":"Mistral Medium (latest)","family":"mistral-medium","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-29","last_updated":"2026-04-29","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":1.5,"output":7.5},"limit":{"context":262144,"output":262144}}}},"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}},"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-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}},"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}},"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.5-9b":{"id":"qwen3.5-9b","name":"Qwen3.5-9B","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-15","last_updated":"2026-02-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.15},"limit":{"context":262144,"output":262144}},"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}},"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}},"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}}}},"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":{"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}},"zai-org/GLM-5.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"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,"cache_read":0.5},"limit":{"context":202752,"output":202752}},"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}},"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}},"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,"cache_read":0.06},"limit":{"context":196608,"output":196608}}}},"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":{"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax-m2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.47,"output":1.4},"limit":{"context":202752,"output":196072}},"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.09,"output":5.43},"limit":{"context":200000,"output":200000}},"qwen3-235b-a22b-instruct-2507":{"id":"qwen3-235b-a22b-instruct-2507","name":"Qwen3 235B A22B Instruct 2507","family":"qwen","attachment":false,"reasoning":true,"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.062,"output":0.408},"limit":{"context":131000,"output":131000}},"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}},"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}},"claude-opus4-7":{"id":"claude-opus4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5.6,"output":27.99,"cache_read":0.56,"cache_write":6.99},"limit":{"context":1000000,"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":true,"cost":{"input":1.08,"output":3.44},"limit":{"context":202752,"output":202752}},"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}},"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}},"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}},"codestral-2508":{"id":"codestral-2508","name":"Codestral 2508","family":"mistral","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-03","release_date":"2025-07-30","last_updated":"2025-07-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.9,"cache_read":0.03},"limit":{"context":256000,"output":256000}},"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}},"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}},"nemotron-3-super-120b-a12b":{"id":"nemotron-3-super-120b-a12b","name":"Nemotron 3 Super 120B A12B","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-12","release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.266,"output":0.799},"limit":{"context":262144,"output":262144}},"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}},"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}},"claude-opus4-6":{"id":"claude-opus4-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-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5.98,"output":29.89},"limit":{"context":1000000,"output":1000000}},"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}},"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}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-14","last_updated":"2026-04-14","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.31,"output":4.1,"cache_read":0.24},"limit":{"context":204800,"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}},"claude-opus4-5":{"id":"claude-opus4-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.98,"output":29.89},"limit":{"context":200000,"output":200000}},"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}},"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}},"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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.81,"output":3.54,"cache_read":0.2},"limit":{"context":256000,"output":256000}},"qwen3-coder-next":{"id":"qwen3-coder-next","name":"Qwen3 Coder Next 80B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-02-04","last_updated":"2026-02-04","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.158,"output":0.84},"limit":{"context":256000,"output":65536}},"claude-4-6-sonnet":{"id":"claude-4-6-sonnet","name":"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-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3.59,"output":17.92},"limit":{"context":1000000,"output":1000000}},"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}},"mixtral-8x7B-instruct-v0.1":{"id":"mixtral-8x7B-instruct-v0.1","name":"Mixtral 8x7B Instruct v0.1","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"knowledge":"2023-09","release_date":"2023-12-11","last_updated":"2023-12-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.438,"output":0.68},"limit":{"context":32000,"output":32000}},"hermes-4-70b":{"id":"hermes-4-70b","name":"Hermes 4 70B","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2023-12","release_date":"2025-08-26","last_updated":"2025-08-26","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.116,"output":0.358},"limit":{"context":128000,"output":128000}},"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.32,"output":1.18},"limit":{"context":196608,"output":196608}},"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.266,"output":0.444},"limit":{"context":163840,"output":163840}},"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}},"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}},"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}},"qwen-2.5-72b-instruct":{"id":"qwen-2.5-72b-instruct","name":"Qwen2.5 72B Instruct","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-06","release_date":"2024-09-19","last_updated":"2024-09-19","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.062,"output":0.231},"limit":{"context":33000,"output":33000}},"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":0.585,"output":2.307},"limit":{"context":164000,"output":164000}},"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}},"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}},"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}},"qwen3.5-122b-a10b":{"id":"qwen3.5-122b-a10b","name":"Qwen3.5 122B A10B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2026-01","release_date":"2026-02-24","last_updated":"2026-02-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.444,"output":3.106},"limit":{"context":262144,"output":262144}},"llama-3.3-70b-instruct":{"id":"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.089,"output":0.275},"limit":{"context":131000,"output":131000}},"mistral-large-2512":{"id":"mistral-large-2512","name":"Mistral Large 3 2512","family":"mistral-large","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-12","release_date":"2025-12-01","last_updated":"2025-12-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":1.5,"cache_read":0.05},"limit":{"context":256000,"output":256000}},"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":"2026-01","release_date":"2026-02-16","last_updated":"2026-02-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":250000,"output":250000}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.133,"output":0.266,"cache_read":0.028},"limit":{"context":1048576,"output":384000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.553,"output":3.106,"cache_read":0.145},"limit":{"context":1048576,"output":384000}},"qwen3-coder-30b-a3b-instruct":{"id":"qwen3-coder-30b-a3b-instruct","name":"Qwen3 Coder 30B A3B Instruct","family":"qwen","attachment":false,"reasoning":true,"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.053,"output":0.222},"limit":{"context":262000,"output":262000}}}},"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}},"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/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-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-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/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-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-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/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-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-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-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/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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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/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/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-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-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-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/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}},"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}},"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-5V-Turbo":{"id":"zai-org/GLM-5V-Turbo","name":"zai-org/GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_write":0},"limit":{"context":200000,"output":131072}},"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-5.1":{"id":"zai-org/GLM-5.1","name":"zai-org/GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_write":0},"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}},"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}},"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.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.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}},"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}},"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}},"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}},"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}},"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-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":{"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-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":{"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-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-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.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}},"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}},"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}},"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}},"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-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}},"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}},"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.6":{"id":"moonshotai/Kimi-K2.6","name":"moonshotai/Kimi-K2.6","family":"kimi","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262000,"output":262000}},"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,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"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.25},"limit":{"context":262000,"output":262000}},"MiniMaxAI/MiniMax-M2.5":{"id":"MiniMaxAI/MiniMax-M2.5","name":"MiniMaxAI/MiniMax-M2.5","family":"minimax","attachment":false,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2026-02-15","last_updated":"2026-02-15","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2},"limit":{"context":197000,"output":131000}},"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}},"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}}}},"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":{"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.6-27b":{"id":"alibaba/qwen3.6-27b","name":"Qwen 3.6 27B","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":3.5999999999999996},"limit":{"context":256000,"output":256000}},"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/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/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.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.6-plus":{"id":"alibaba/qwen3.6-plus","name":"Qwen 3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.09999999999999999,"cache_write":0.625},"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/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-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/qwen-3.6-max-preview":{"id":"alibaba/qwen-3.6-max-preview","name":"Qwen 3.6 Max Preview","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":1.3,"output":7.8,"cache_read":0.26,"cache_write":1.625},"limit":{"context":240000,"output":64000}},"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-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-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-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-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-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-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-235b-a22b-instruct":{"id":"alibaba/qwen3-vl-235b-a22b-instruct","name":"Qwen3 VL 235B A22B Instruct","family":"qwen","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2025-09-24","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.39999999999999997,"output":1.5999999999999999},"limit":{"context":131072,"output":129024}},"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/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.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/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-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-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}},"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}},"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-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}},"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-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-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}},"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":{"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-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-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}},"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}},"arcee-ai/trinity-large-thinking":{"id":"arcee-ai/trinity-large-thinking","name":"Trinity Large Thinking","family":"trinity","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":0.8999999999999999},"limit":{"context":262100,"output":80000}},"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}},"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}},"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}},"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-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-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-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-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-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-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}},"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-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-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}},"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}},"zai/glm-5v-turbo":{"id":"zai/glm-5v-turbo","name":"GLM 5V Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":200000,"output":128000}},"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-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-5.1":{"id":"zai/glm-5.1","name":"GLM 5.1","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202752,"output":202752}},"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.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.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-5-turbo":{"id":"zai/glm-5-turbo","name":"GLM 5 Turbo","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-15","last_updated":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":202800,"output":131100}},"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.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.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}},"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}},"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}},"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}},"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}},"xai/grok-4.3":{"id":"xai/grok-4.3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-30","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.19999999999999998},"limit":{"context":1000000,"output":1000000}},"xai/grok-4.20-non-reasoning":{"id":"xai/grok-4.20-non-reasoning","name":"Grok 4.20 Non-Reasoning","family":"grok","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-09","last_updated":"2026-03-23","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-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-4.20-reasoning":{"id":"xai/grok-4.20-reasoning","name":"Grok 4.20 Reasoning","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-09","last_updated":"2026-03-23","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.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-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-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.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-4.20-multi-agent":{"id":"xai/grok-4.20-multi-agent","name":"Grok 4.20 Multi-Agent","family":"grok","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-09","last_updated":"2026-03-23","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6,"cache_read":0.19999999999999998},"limit":{"context":2000000,"output":2000000}},"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-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":{"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-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-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}},"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-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-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-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}},"nvidia/nemotron-3-super-120b-a12b":{"id":"nvidia/nemotron-3-super-120b-a12b","name":"NVIDIA Nemotron 3 Super 120B A12B","family":"nemotron","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.65},"limit":{"context":256000,"output":32000}},"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}},"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}},"inception/mercury-edit-2":{"id":"inception/mercury-edit-2","name":"Mercury Edit 2","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":0.75,"cache_read":0.025},"limit":{"context":128000,"output":8192}},"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,"release_date":"2025-02-26","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1},"limit":{"context":32000,"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-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-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/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-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/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-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/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-5.5":{"id":"openai/gpt-5.5","name":"GPT 5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":1000000,"input":872000,"output":128000}},"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/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-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.4-mini":{"id":"openai/gpt-5.4-mini","name":"GPT 5.4 Mini","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000}},"openai/gpt-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"GPT 5.4 Nano","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.19999999999999998,"output":1.25,"cache_read":0.02},"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/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.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-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-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/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/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/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/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-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/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-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.5-pro":{"id":"openai/gpt-5.5-pro","name":"GPT 5.5 Pro","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180},"limit":{"context":1000000,"input":872000,"output":128000}},"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-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-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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":1.6,"cache_read":0.1},"limit":{"context":1047576,"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","pdf"],"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":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-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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10,"cache_read":1.25},"limit":{"context":128000,"output":16384}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"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,"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/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}},"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/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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":60,"cache_read":7.5},"limit":{"context":200000,"output":100000}},"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-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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6,"cache_read":0.08},"limit":{"context":128000,"output":16384}},"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-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}},"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-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/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-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-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}},"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/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/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/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/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/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/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-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/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/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/mistral-small":{"id":"mistral/mistral-small","name":"Mistral Small (latest)","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":256000,"output":256000}},"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/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/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/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/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}},"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}},"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.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-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-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-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.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-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-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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"Minimax M2.7","family":"minimax","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131000}},"minimax/minimax-m2.7-highspeed":{"id":"minimax/minimax-m2.7-highspeed","name":"MiniMax M2.7 High Speed","family":"minimax","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":2.4,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131100}},"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.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.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":{"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}},"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}},"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}},"kwaipilot/kat-coder-pro-v2":{"id":"kwaipilot/kat-coder-pro-v2","name":"Kat Coder Pro V2","family":"kat-coder","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":256000,"output":256000}},"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/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/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-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-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/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-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/gemma-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Gemma 4 31B IT","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.14,"output":0.39999999999999997},"limit":{"context":262144,"output":131072}},"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/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/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-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":{"id":"google/gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","family":"gemini","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-05-07","last_updated":"2026-05-08","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1.5,"cache_read":0.03},"limit":{"context":1000000,"output":65000}},"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-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-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-embedding-2":{"id":"google/gemini-embedding-2","name":"Gemini Embedding 2","family":"gemini-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-10","last_updated":"2026-03-23","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":0,"output":0}},"google/gemma-4-26b-a4b-it":{"id":"google/gemma-4-26b-a4b-it","name":"Gemma 4 26B A4B IT","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.13,"output":0.39999999999999997},"limit":{"context":262144,"output":131072}},"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/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-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/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.03,"input_audio":1},"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,"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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"limit":{"context":1048576,"output":65536}},"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}},"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.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-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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262000,"output":262000}},"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":{"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"},"interfaze/interfaze-beta":{"id":"interfaze/interfaze-beta","name":"Interfaze Beta","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2025-10-07","last_updated":"2026-04-29","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.5,"output":3.5},"limit":{"context":1000000,"output":32000}},"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-31","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-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-16","last_updated":"2026-04-16","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-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-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-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-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":128000}},"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-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":{"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-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.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.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-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-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}},"xiaomi/mimo-v2.5-pro":{"id":"xiaomi/mimo-v2.5-pro","name":"MiMo V2.5 Pro","family":"mimo-v2.5-pro","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.19999999999999998},"limit":{"context":1050000,"output":131000}},"xiaomi/mimo-v2.5":{"id":"xiaomi/mimo-v2.5","name":"MiMo M2.5","family":"mimo-v2.5","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-05-01","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.39999999999999997,"output":2,"cache_read":0.08},"limit":{"context":1050000,"output":131100}},"xiaomi/mimo-v2-pro":{"id":"xiaomi/mimo-v2-pro","name":"MiMo V2 Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.19999999999999998},"limit":{"context":1000000,"output":128000}},"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}},"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}},"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}},"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}},"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-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-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-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}}}},"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":{"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":{"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.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131072}},"MiniMax-M2.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"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}}}},"llmgateway":{"id":"llmgateway","env":["LLMGATEWAY_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.llmgateway.io/v1","name":"LLM Gateway","doc":"https://llmgateway.io/docs","models":{"gpt-4o-mini-search-preview":{"id":"gpt-4o-mini-search-preview","name":"GPT-4o Mini Search Preview","family":"gpt","attachment":true,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2024-10-01","last_updated":"2024-10-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6},"limit":{"context":128000,"output":16384}},"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,"structured_output":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}},"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,"structured_output":true,"temperature":true,"release_date":"2025-07-08","last_updated":"2025-07-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":131072,"output":8192}},"llama-4-scout":{"id":"llama-4-scout","name":"Llama 4 Scout","family":"llama","attachment":false,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2025-04-05","last_updated":"2025-04-05","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.18,"output":0.59},"limit":{"context":32768,"output":16384},"status":"beta"},"hermes-2-pro-llama-3-8b":{"id":"hermes-2-pro-llama-3-8b","name":"Hermes 2 Pro Llama 3 8B","family":"hermes","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2024-05-27","last_updated":"2024-05-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.14},"limit":{"context":8192,"output":8192}},"qwen-coder-plus":{"id":"qwen-coder-plus","name":"Qwen Coder Plus","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2024-09-18","last_updated":"2024-09-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":1},"limit":{"context":131072,"output":8192}},"auto":{"id":"auto","name":"Auto Route","family":"auto","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2024-01-01","last_updated":"2024-01-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":128000,"output":16384}},"glm-4.6v-flashx":{"id":"glm-4.6v-flashx","name":"GLM-4.6V FlashX","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-08","last_updated":"2025-12-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.04,"output":0.4,"cache_read":0},"limit":{"context":128000,"output":16000}},"gemma-2-27b-it-together":{"id":"gemma-2-27b-it-together","name":"Gemma 2 27B IT","family":"gemma","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2024-06-27","last_updated":"2024-06-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.08,"output":0.08},"limit":{"context":8192,"output":16384}},"codestral-2508":{"id":"codestral-2508","name":"Codestral","family":"mistral","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-07-30","last_updated":"2025-07-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.9},"limit":{"context":256000,"output":16384}},"gemma-3-1b-it":{"id":"gemma-3-1b-it","name":"Gemma 3 1B IT","family":"gemma","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2025-03-12","last_updated":"2025-03-12","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.08,"output":0.3},"limit":{"context":1000000,"output":16384}},"glm-4-32b-0414-128k":{"id":"glm-4-32b-0414-128k","name":"GLM-4 32B (0414-128k)","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-14","last_updated":"2025-04-14","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.1,"output":0.1},"limit":{"context":128000,"output":16384}},"seed-1-6-flash-250715":{"id":"seed-1-6-flash-250715","name":"Seed 1.6 Flash (250715)","family":"seed","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-07-26","last_updated":"2025-07-26","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.07,"output":0.3,"cache_read":0.01},"limit":{"context":256000,"output":8192}},"seed-1-6-250615":{"id":"seed-1-6-250615","name":"Seed 1.6 (250615)","family":"seed","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-06-25","last_updated":"2025-06-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2,"cache_read":0.05},"limit":{"context":256000,"output":8192}},"qwen3-vl-235b-a22b-thinking":{"id":"qwen3-vl-235b-a22b-thinking","name":"Qwen3 VL 235B A22B Thinking","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-09-15","last_updated":"2025-09-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":131072,"output":8192}},"qwen3-vl-30b-a3b-thinking":{"id":"qwen3-vl-30b-a3b-thinking","name":"Qwen3 VL 30B A3B Thinking","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-10-02","last_updated":"2025-10-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"qwen2-5-vl-32b-instruct":{"id":"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-15","last_updated":"2025-03-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.3},"limit":{"context":131072,"output":8192}},"qwen3-vl-8b-instruct":{"id":"qwen3-vl-8b-instruct","name":"Qwen3 VL 8B Instruct","family":"qwen","attachment":true,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2025-08-19","last_updated":"2025-08-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"claude-3-7-sonnet":{"id":"claude-3-7-sonnet","name":"Claude 3.7 Sonnet","family":"claude","attachment":false,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2025-02-24","last_updated":"2025-02-24","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15,"cache_read":0.3},"limit":{"context":200000,"output":8192}},"gemini-pro-latest":{"id":"gemini-pro-latest","name":"Gemini Pro Latest","family":"gemini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-27","last_updated":"2026-02-27","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":12,"cache_read":0.2},"limit":{"context":1048576,"output":65536}},"claude-3-5-haiku":{"id":"claude-3-5-haiku","name":"Claude 3.5 Haiku","family":"claude","attachment":false,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2024-10-22","last_updated":"2024-10-22","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":4,"cache_read":0.08},"limit":{"context":200000,"output":8192},"status":"deprecated"},"qwen-max-latest":{"id":"qwen-max-latest","name":"Qwen Max Latest","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-01-25","last_updated":"2025-01-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.6,"output":6.4},"limit":{"context":32768,"output":8192}},"glm-4.6v-flash":{"id":"glm-4.6v-flash","name":"GLM-4.6V Flash","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-08","last_updated":"2025-12-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":128000,"output":16000},"status":"beta"},"qwen3-30b-a3b-instruct-2507":{"id":"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-08","last_updated":"2025-07-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"minimax-text-01":{"id":"minimax-text-01","name":"MiniMax Text 01","family":"minimax","attachment":false,"reasoning":true,"tool_call":false,"structured_output":false,"temperature":true,"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":1000000,"output":131072}},"qwen3-32b-fp8":{"id":"qwen3-32b-fp8","name":"Qwen3 32B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"llama-4-scout-17b-instruct":{"id":"llama-4-scout-17b-instruct","name":"Llama 4 Scout 17B Instruct","family":"llama","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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.17,"output":0.66},"limit":{"context":8192,"output":2048}},"qwen3-4b-fp8":{"id":"qwen3-4b-fp8","name":"Qwen3 4B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.03,"output":0.05},"limit":{"context":131072,"output":8192}},"ministral-8b-2512":{"id":"ministral-8b-2512","name":"Ministral 8B","family":"mistral","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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.15,"output":0.15},"limit":{"context":262144,"output":8192}},"gemma-3-27b":{"id":"gemma-3-27b","name":"Gemma 3 27B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2025-03-12","last_updated":"2025-03-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.27,"output":0.27},"limit":{"context":128000,"output":16384}},"qwen3-vl-flash":{"id":"qwen3-vl-flash","name":"Qwen3 VL Flash","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-10-09","last_updated":"2025-10-09","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.05,"output":0.4,"cache_read":0.01},"limit":{"context":1000000,"output":32000}},"llama-3.1-70b-instruct":{"id":"llama-3.1-70b-instruct","name":"Llama 3.1 70B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"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":2048},"status":"beta"},"seed-1-8-251228":{"id":"seed-1-8-251228","name":"Seed 1.8 (251228)","family":"seed","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-18","last_updated":"2025-12-18","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2,"cache_read":0.05},"limit":{"context":256000,"output":8192}},"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,"structured_output":true,"temperature":true,"release_date":"2025-07-08","last_updated":"2025-07-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":131072,"output":8192}},"seed-1-6-250915":{"id":"seed-1-6-250915","name":"Seed 1.6 (250915)","family":"seed","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-09-15","last_updated":"2025-09-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2,"cache_read":0.05},"limit":{"context":256000,"output":8192}},"glm-4.5-x":{"id":"glm-4.5-x","name":"GLM-4.5 X","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-07-28","last_updated":"2025-07-28","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":2.2,"output":8.9,"cache_read":0.45},"limit":{"context":128000,"output":16384},"status":"beta"},"qwen3-30b-a3b-thinking-2507":{"id":"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-08","last_updated":"2025-07-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"grok-4-fast-reasoning":{"id":"grok-4-fast-reasoning","name":"Grok 4 Fast Reasoning","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":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,"cache_read":0.05},"limit":{"context":2000000,"output":30000}},"deepseek-v3.1":{"id":"deepseek-v3.1","name":"DeepSeek V3.1","family":"deepseek","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-08-21","last_updated":"2025-08-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.56,"output":1.68,"cache_read":0.11},"limit":{"context":128000,"output":32768}},"ministral-3b-2512":{"id":"ministral-3b-2512","name":"Ministral 3B","family":"mistral","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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":131072,"output":8192}},"qwen-plus-latest":{"id":"qwen-plus-latest","name":"Qwen Plus Latest","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-01-25","last_updated":"2025-01-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":0.9},"limit":{"context":131072,"output":8192}},"llama-3.1-nemotron-ultra-253b":{"id":"llama-3.1-nemotron-ultra-253b","name":"Llama 3.1 Nemotron Ultra 253B","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-04-07","last_updated":"2025-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":1.8},"limit":{"context":128000,"output":8192}},"llama-4-maverick-17b-instruct":{"id":"llama-4-maverick-17b-instruct","name":"Llama 4 Maverick 17B Instruct","family":"llama","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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.24,"output":0.97},"limit":{"context":8192,"output":2048}},"grok-4-0709":{"id":"grok-4-0709","name":"Grok 4 (0709)","family":"grok","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-07-09","last_updated":"2025-07-09","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":256000,"output":256000}},"qwen3-30b-a3b-fp8":{"id":"qwen3-30b-a3b-fp8","name":"Qwen3 30B A3B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"minimax-m2.1-lightning":{"id":"minimax-m2.1-lightning","name":"MiniMax M2.1 Lightning","family":"minimax","attachment":false,"reasoning":true,"tool_call":false,"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.12,"output":0.48},"limit":{"context":196608,"output":131072}},"qwen3-max-2026-01-23":{"id":"qwen3-max-2026-01-23","name":"Qwen3 Max (2026-01-23)","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-01-23","last_updated":"2026-01-23","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15,"cache_read":0.6},"limit":{"context":256000,"output":32800}},"llama-3.2-3b-instruct":{"id":"llama-3.2-3b-instruct","name":"Llama 3.2 3B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-09-18","last_updated":"2024-09-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.03,"output":0.05},"limit":{"context":32768,"output":32000}},"qwen3-coder-next":{"id":"qwen3-coder-next","name":"Qwen3 Coder Next","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-10-15","last_updated":"2025-10-15","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":4},"limit":{"context":262144,"output":65536}},"gpt-4o-search-preview":{"id":"gpt-4o-search-preview","name":"GPT-4o Search Preview","family":"gpt","attachment":true,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2024-10-01","last_updated":"2024-10-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10},"limit":{"context":128000,"output":16384}},"custom":{"id":"custom","name":"Custom Model","family":"auto","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2024-01-01","last_updated":"2024-01-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":128000,"output":16384}},"qwen3-vl-30b-a3b-instruct":{"id":"qwen3-vl-30b-a3b-instruct","name":"Qwen3 VL 30B A3B Instruct","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-10-02","last_updated":"2025-10-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"deepseek-v3.2":{"id":"deepseek-v3.2","name":"DeepSeek V3.2","family":"deepseek","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-09-29","last_updated":"2025-09-29","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.28,"output":0.42,"cache_read":0.03},"limit":{"context":163840,"output":16384}},"qwen3-235b-a22b-fp8":{"id":"qwen3-235b-a22b-fp8","name":"Qwen3 235B A22B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":2.5},"limit":{"context":131072,"output":8192}},"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":"2025-08-05","last_updated":"2025-08-05","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.1,"output":0.5},"limit":{"context":131072,"output":32766}},"kimi-k2":{"id":"kimi-k2","name":"Kimi K2","family":"kimi","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":1,"output":3,"cache_read":0.5},"limit":{"context":131072,"output":16384}},"llama-3-8b-instruct":{"id":"llama-3-8b-instruct","name":"Llama 3 8B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-04-03","last_updated":"2025-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.04,"output":0.04},"limit":{"context":8192,"output":8192}},"qwen3-vl-235b-a22b-instruct":{"id":"qwen3-vl-235b-a22b-instruct","name":"Qwen3 VL 235B A22B Instruct","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-09-15","last_updated":"2025-09-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":131072,"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,"release_date":"2025-08-05","last_updated":"2025-08-05","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.75},"limit":{"context":131072,"output":32766}},"qwen25-coder-7b":{"id":"qwen25-coder-7b","name":"Qwen2.5 Coder 7B","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-09-19","last_updated":"2024-09-19","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.05,"output":0.05},"limit":{"context":131072,"output":8192}},"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,"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.22,"output":0.22},"limit":{"context":128000,"output":2048},"status":"beta"},"llama-3-70b-instruct":{"id":"llama-3-70b-instruct","name":"Llama 3 70B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-04-18","last_updated":"2024-04-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.51,"output":0.74},"limit":{"context":8192,"output":8000}},"deepseek-r1-0528":{"id":"deepseek-r1-0528","name":"DeepSeek R1 (0528)","family":"deepseek","attachment":false,"reasoning":true,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2025-05-28","last_updated":"2025-05-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":64000,"output":16384},"status":"beta"},"glm-4.5-airx":{"id":"glm-4.5-airx","name":"GLM-4.5 AirX","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-07-28","last_updated":"2025-07-28","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.1,"output":4.5,"cache_read":0.22},"limit":{"context":128000,"output":16384}},"ministral-14b-2512":{"id":"ministral-14b-2512","name":"Ministral 14B","family":"mistral","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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.2,"output":0.2},"limit":{"context":262144,"output":8192}},"llama-3.2-11b-instruct":{"id":"llama-3.2-11b-instruct","name":"Llama 3.2 11B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-09-25","last_updated":"2024-09-25","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.07,"output":0.33},"limit":{"context":128000,"output":8192}},"claude-3-opus":{"id":"claude-3-opus","name":"Claude 3 Opus","family":"claude","attachment":true,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2024-03-04","last_updated":"2024-03-04","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":75,"cache_read":1.5},"limit":{"context":200000,"output":4096}},"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131072}},"grok-4-20-beta-0309-non-reasoning":{"id":"grok-4-20-beta-0309-non-reasoning","name":"Grok 4.20 (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}},"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}},"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-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}},"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}},"kimi-k2.5":{"id":"kimi-k2.5","name":"Kimi K2.5","family":"kimi-k2.5","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}},"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}},"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}},"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}},"minimax-m2.7-highspeed":{"id":"minimax-m2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"gemma-3n-e4b-it":{"id":"gemma-3n-e4b-it","name":"Gemma 3n 4B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-31","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}},"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}},"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}},"qwen35-397b-a17b":{"id":"qwen35-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-15","last_updated":"2026-02-15","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":65536}},"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}},"gpt-5.3-chat-latest":{"id":"gpt-5.3-chat-latest","name":"GPT-5.3 Chat (latest)","family":"gpt","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"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}},"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-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}},"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}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"experimental":{"modes":{"fast":{"cost":{"input":12.5,"output":75,"cache_read":1.25},"provider":{"body":{"service_tier":"priority"}}}}}},"qwen3.6-35b-a3b":{"id":"qwen3.6-35b-a3b","name":"Qwen3.6 35B-A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.248,"output":1.485},"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}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"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-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}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":131072}},"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}},"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}},"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}},"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}},"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}},"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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"limit":{"context":1048576,"output":65536}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"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,"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}},"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}},"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}},"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}},"grok-4-20-beta-0309-reasoning":{"id":"grok-4-20-beta-0309-reasoning","name":"Grok 4.20 (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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6,"cache_read":0.08},"limit":{"context":128000,"output":16384}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.276,"output":1.651,"cache_read":0.028,"cache_write":0.344},"limit":{"context":1000000,"output":65536}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":24,"cache_read":1.3,"cache_write":0},"limit":{"context":200000,"output":131072}},"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-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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-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}},"gemma-3-12b-it":{"id":"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,"output":0},"limit":{"context":32768,"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":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}},"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.03,"input_audio":1},"limit":{"context":1048576,"output":65536}},"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}},"gemma-3n-e2b-it":{"id":"gemma-3n-e2b-it","name":"Gemma 3n 2B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","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}},"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}},"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}},"gemini-3.1-flash-lite":{"id":"gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","family":"gemini-flash-lite","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-05-07","last_updated":"2026-05-07","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}},"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}},"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}},"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}},"gemma-3-4b-it":{"id":"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,"output":0},"limit":{"context":32768,"output":8192}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":60,"cache_read":7.5},"limit":{"context":200000,"output":100000}},"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}},"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":1050000,"input":922000,"output":128000}},"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}},"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}},"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}},"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}},"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}},"grok-4-3":{"id":"grok-4-3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2,"context_over_200k":{"input":2.5,"output":5,"cache_read":0.4}},"limit":{"context":1000000,"output":30000}},"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}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"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}},"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}},"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}},"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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}},"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}},"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}},"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}},"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}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":200000,"output":100000}},"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":{"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10,"cache_read":1.25},"limit":{"context":128000,"output":16384}},"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}},"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}},"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}},"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-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}},"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}},"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":262144,"output":65536}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.3,"output":7.8,"cache_read":0.13,"cache_write":1.625},"limit":{"context":262144,"output":65536}},"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}},"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}},"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}},"gpt-5.5-pro":{"id":"gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":1047576,"output":32768}},"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}},"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}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":1.6,"cache_read":0.1},"limit":{"context":1047576,"output":32768}},"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}},"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-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}},"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}}}},"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-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}},"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-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-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-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}},"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.2-maas":{"id":"deepseek-ai/deepseek-v3.2-maas","name":"DeepSeek V3.2","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-17","last_updated":"2026-04-04","modalities":{"input":["text","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0.56,"output":1.68,"cache_read":0.056},"limit":{"context":163840,"output":65536},"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"}},"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}},"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}},"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"}},"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"}},"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"}},"moonshotai/kimi-k2-thinking-maas":{"id":"moonshotai/kimi-k2-thinking-maas","name":"Kimi K2 Thinking","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-08","release_date":"2025-11-13","last_updated":"2025-11-13","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":2.5},"limit":{"context":262144,"output":262144},"provider":{"npm":"@ai-sdk/openai-compatible","api":"https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi"}},"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-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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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-31","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,"context_over_200k":{"input":6,"output":22.5,"cache_read":0.6,"cache_write":7.5}},"limit":{"context":200000,"output":64000},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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-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-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-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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"gemini-3.1-flash-lite":{"id":"gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","family":"gemini-flash-lite","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-05-07","last_updated":"2026-05-07","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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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-31","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,"context_over_200k":{"input":10,"output":37.5,"cache_read":1,"cache_write":12.5}},"limit":{"context":1000000,"output":128000},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"claude-opus-4-7@default":{"id":"claude-opus-4-7@default","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}}}},"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/nvidia/nemotron-3-120b-a12b":{"id":"@cf/nvidia/nemotron-3-120b-a12b","name":"Nemotron 3 Super 120B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":1.5},"limit":{"context":256000,"output":256000}},"@cf/openai/gpt-oss-20b":{"id":"@cf/openai/gpt-oss-20b","name":"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.2,"output":0.3},"limit":{"context":128000,"output":16384}},"@cf/openai/gpt-oss-120b":{"id":"@cf/openai/gpt-oss-120b","name":"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.35,"output":0.75},"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":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-04-16","last_updated":"2025-04-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.27,"output":0.85},"limit":{"context":128000,"output":16384}},"@cf/google/gemma-4-26b-a4b-it":{"id":"@cf/google/gemma-4-26b-a4b-it","name":"Gemma 4 26B A4B IT","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-12-15","last_updated":"2025-12-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.3},"limit":{"context":256000,"output":16384}},"@cf/moonshotai/kimi-k2.5":{"id":"@cf/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"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3,"cache_read":0.1},"limit":{"context":256000,"output":256000}},"@cf/moonshotai/kimi-k2.6":{"id":"@cf/moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":256000,"output":256000}}}},"groq":{"id":"groq","env":["GROQ_API_KEY"],"npm":"@ai-sdk/groq","name":"Groq","doc":"https://console.groq.com/docs/models","models":{"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"},"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"},"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"},"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"},"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}},"allam-2-7b":{"id":"allam-2-7b","name":"ALLaM-2-7b","family":"allam","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-09","release_date":"2024-09","last_updated":"2024-09","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":4096,"output":4096}},"whisper-large-v3":{"id":"whisper-large-v3","name":"Whisper Large V3","family":"whisper","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"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":448,"output":448}},"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}},"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"},"whisper-large-v3-turbo":{"id":"whisper-large-v3-turbo","name":"Whisper Large v3 Turbo","family":"whisper","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","release_date":"2024-10-01","last_updated":"2024-10-01","modalities":{"input":["audio"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":448,"output":448}},"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"},"canopylabs/orpheus-arabic-saudi":{"id":"canopylabs/orpheus-arabic-saudi","name":"Orpheus Arabic Saudi","family":"canopylabs","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2025-12-16","release_date":"2025-12-16","last_updated":"2025-12-16","modalities":{"input":["text"],"output":["audio"]},"open_weights":false,"cost":{"input":40,"output":0},"limit":{"context":4000,"output":50000}},"canopylabs/orpheus-v1-english":{"id":"canopylabs/orpheus-v1-english","name":"Orpheus V1 English","family":"canopylabs","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2025-12-19","release_date":"2025-12-19","last_updated":"2025-12-19","modalities":{"input":["text"],"output":["audio"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":4000,"output":50000}},"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-prompt-guard-2-22m":{"id":"meta-llama/llama-prompt-guard-2-22m","name":"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":true,"cost":{"input":0.03,"output":0.03},"limit":{"context":512,"output":512}},"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},"status":"deprecated"},"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},"status":"deprecated"},"meta-llama/llama-prompt-guard-2-86m":{"id":"meta-llama/llama-prompt-guard-2-86m","name":"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":true,"cost":{"input":0.04,"output":0.04},"limit":{"context":512,"output":512}},"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}},"openai/gpt-oss-safeguard-20b":{"id":"openai/gpt-oss-safeguard-20b","name":"Safety GPT OSS 20B","family":"gpt-oss","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.075,"output":0.3,"cache_read":0.037},"limit":{"context":131072,"output":65536}},"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}},"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":40960}},"groq/compound":{"id":"groq/compound","name":"Compound","family":"groq","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-09-04","release_date":"2025-09-04","last_updated":"2025-09-04","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":8192}},"groq/compound-mini":{"id":"groq/compound-mini","name":"Compound Mini","family":"groq","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-09-04","release_date":"2025-09-04","last_updated":"2025-09-04","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":8192}},"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}}}},"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":{"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-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}},"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-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.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-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}},"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-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}},"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}},"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"}},"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}},"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"}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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"},"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-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}},"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-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"}},"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}},"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.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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"grok-4-20-non-reasoning":{"id":"grok-4-20-non-reasoning","name":"Grok 4.20 (Non-Reasoning)","family":"grok","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-09","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":262000,"output":8192},"status":"beta"},"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}},"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}},"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}},"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}},"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-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}},"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-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}},"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-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}},"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-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}},"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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":false,"reasoning":true,"tool_call":true,"interleaved":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4},"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-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}},"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}},"grok-4-20-reasoning":{"id":"grok-4-20-reasoning","name":"Grok 4.20 (Reasoning)","family":"grok","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-09","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":262000,"output":8192},"status":"beta"},"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}},"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,"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"}},"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}},"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}},"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}},"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}},"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"}},"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-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}},"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-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}},"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}},"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}},"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}},"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.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}},"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}},"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}},"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}},"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}},"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,"knowledge":"2025-08-31","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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1"}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"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-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-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}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000}},"cohere-command-r-08-2024":{"id":"cohere-command-r-08-2024","name":"Command R","family":"command-r","attachment":false,"reasoning":false,"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}},"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.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"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":"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}},"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}},"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":{"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.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}},"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}},"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}},"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}},"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-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}},"cohere-command-r-plus-08-2024":{"id":"cohere-command-r-plus-08-2024","name":"Command R+","family":"command-r","attachment":false,"reasoning":false,"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-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}},"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}},"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-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}}}},"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":{"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}},"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}},"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-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}},"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":{"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-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}},"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"},"temperature":true,"release_date":"2026-02-11","last_updated":"2026-02-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":3.15},"limit":{"context":204800,"output":131072}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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":{"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}},"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}},"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}},"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}},"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}}}},"tencent-coding-plan":{"id":"tencent-coding-plan","env":["TENCENT_CODING_PLAN_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.lkeap.cloud.tencent.com/coding/v3","name":"Tencent Coding Plan (China)","doc":"https://cloud.tencent.com/document/product/1772/128947","models":{"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}},"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}},"hunyuan-turbos":{"id":"hunyuan-turbos","name":"Hunyuan-TurboS","family":"hunyuan","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":16384}},"hunyuan-t1":{"id":"hunyuan-t1","name":"Hunyuan-T1","family":"hunyuan","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":16384}},"hunyuan-2.0-instruct":{"id":"hunyuan-2.0-instruct","name":"Tencent HY 2.0 Instruct","family":"hunyuan","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"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":204800,"output":32768}},"tc-code-latest":{"id":"tc-code-latest","name":"Auto","family":"auto","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":16384}},"hunyuan-2.0-thinking":{"id":"hunyuan-2.0-thinking","name":"Tencent HY 2.0 Think","family":"hunyuan","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":16384}}}},"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}},"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}},"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}},"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/models","models":{"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-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-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-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-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-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-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}},"gemma-3n-e4b-it":{"id":"gemma-3n-e4b-it","name":"Gemma 3n 4B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","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}},"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-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-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-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-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-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.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-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-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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"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}},"gemma-4-31b-it":{"id":"gemma-4-31b-it","name":"Gemma 4 31B","family":"gemma","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":256000,"output":8192}},"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-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-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}},"gemma-3-12b-it":{"id":"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,"output":0},"limit":{"context":32768,"output":8192}},"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.03,"input_audio":1},"limit":{"context":1048576,"output":65536}},"gemma-3n-e2b-it":{"id":"gemma-3n-e2b-it","name":"Gemma 3n 2B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","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}},"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":{"id":"gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","family":"gemini-flash-lite","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-05-07","last_updated":"2026-05-07","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}},"gemma-3-4b-it":{"id":"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,"output":0},"limit":{"context":32768,"output":8192}},"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-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-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}},"gemma-3-27b-it":{"id":"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,"output":0},"limit":{"context":131072,"output":8192}},"gemma-4-26b-a4b-it":{"id":"gemma-4-26b-a4b-it","name":"Gemma 4 26B","family":"gemma","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":256000,"output":8192}},"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-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-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-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}}}},"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-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}},"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}}}},"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-k2.5","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-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.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"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-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":{"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}}}},"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.77,"output":2.75},"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.33,"output":0.33},"limit":{"context":32000,"output":8192}},"mistralai/Mistral-Medium-3.5-128B":{"id":"mistralai/Mistral-Medium-3.5-128B","name":"Mistral Medium 3.5 128B","family":"mistral-medium","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2026-04","release_date":"2026-04-29","last_updated":"2026-04-29","modalities":{"input":["image","text"],"output":["text"]},"open_weights":true,"cost":{"input":1.65,"output":5.5},"limit":{"context":262144,"output":131072}},"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.99,"output":0.99},"limit":{"context":128000,"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.44,"output":0.99},"limit":{"context":128000,"output":8192}},"google/gemma-4-31B-it":{"id":"google/gemma-4-31B-it","name":"Gemma 4 31B Instruct","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-12","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["audio","image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.275,"output":0.55},"limit":{"context":128000,"output":8192}}}},"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":{"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}},"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-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}},"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-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-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.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-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-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-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}},"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-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-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/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-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/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}},"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":false,"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-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":{"id":"cohere/cohere-command-r-plus","name":"Cohere Command R+","family":"command-r","attachment":false,"reasoning":false,"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}},"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-plus-08-2024":{"id":"cohere/cohere-command-r-plus-08-2024","name":"Cohere Command R+ 08-2024","family":"command-r","attachment":false,"reasoning":false,"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}},"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}},"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}},"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-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/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/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/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-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/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/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-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}},"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/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}},"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-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-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/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/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.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.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.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-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-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}},"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/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/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-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/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/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}}}},"neuralwatt":{"id":"neuralwatt","env":["NEURALWATT_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.neuralwatt.com/v1","name":"Neuralwatt","doc":"https://portal.neuralwatt.com/docs","models":{"glm-5-fast":{"id":"glm-5-fast","name":"GLM 5 Fast","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.1,"output":3.6},"limit":{"context":200000,"output":200000}},"kimi-k2.6-fast":{"id":"kimi-k2.6-fast","name":"Kimi K2.6 Fast","family":"kimi","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.69,"output":3.22},"limit":{"context":262144,"output":262144}},"qwen3.5-397b-fast":{"id":"qwen3.5-397b-fast","name":"Qwen3.5 397B Fast","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.69,"output":4.14},"limit":{"context":262144,"output":262144}},"glm-5.1-fast":{"id":"glm-5.1-fast","name":"GLM 5.1 Fast","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.1,"output":3.6},"limit":{"context":200000,"output":200000}},"qwen3.6-35b-fast":{"id":"qwen3.6-35b-fast","name":"Qwen3.6 35B Fast","family":"qwen3.6","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.05,"output":0.1},"limit":{"context":131072,"output":131072}},"kimi-k2.5-fast":{"id":"kimi-k2.5-fast","name":"Kimi K2.5 Fast","family":"kimi","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.52,"output":2.59},"limit":{"context":262144,"output":262144}},"Qwen/Qwen3.5-397B-A17B-FP8":{"id":"Qwen/Qwen3.5-397B-A17B-FP8","name":"Qwen3.5 397B A17B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.69,"output":4.14},"limit":{"context":262144,"output":262144}},"Qwen/Qwen3.6-35B-A3B":{"id":"Qwen/Qwen3.6-35B-A3B","name":"Qwen3.6 35B A3B","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.05,"output":0.1},"limit":{"context":131072,"output":131072}},"zai-org/GLM-5.1-FP8":{"id":"zai-org/GLM-5.1-FP8","name":"GLM 5.1 FP8","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.1,"output":3.6},"limit":{"context":200000,"output":200000}},"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":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-12-09","last_updated":"2025-12-09","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.12,"output":0.35},"limit":{"context":262144,"output":262144}},"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.16},"limit":{"context":16384,"output":16384}},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.69,"output":3.22},"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,"release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.52,"output":2.59},"limit":{"context":262144,"output":262144}},"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.35,"output":1.38},"limit":{"context":196608,"output":196608}}}},"togetherai":{"id":"togetherai","env":["TOGETHER_API_KEY"],"npm":"@ai-sdk/togetherai","name":"Together AI","doc":"https://docs.together.ai/docs/serverless-models","models":{"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}},"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.6-Plus":{"id":"Qwen/Qwen3.6-Plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-30","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":3},"limit":{"context":1000000,"output":500000}},"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-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-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}},"zai-org/GLM-5.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-11","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4},"limit":{"context":202752,"output":131072}},"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}},"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}},"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-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-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":2.1,"output":4.4,"cache_read":0.2},"limit":{"context":512000,"output":384000}},"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}},"google/gemma-4-31B-it":{"id":"google/gemma-4-31B-it","name":"Gemma 4 31B Instruct","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.5},"limit":{"context":262144,"output":131072}},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":1.2,"output":4.5,"cache_read":0.2},"limit":{"context":262144,"output":131000}},"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}},"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}},"MiniMaxAI/MiniMax-M2.7":{"id":"MiniMaxAI/MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":202752,"output":131072}}}},"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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"tencent-tokenhub":{"id":"tencent-tokenhub","env":["TENCENT_TOKENHUB_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://tokenhub.tencentmaas.com/v1","name":"Tencent TokenHub","doc":"https://cloud.tencent.com/document/product/1823/130050","models":{"hy3-preview":{"id":"hy3-preview","name":"Hy3 preview","family":"Hy","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":256000,"output":64000}}}},"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-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-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-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-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-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-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-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-31","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-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-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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-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-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-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-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-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-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-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-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-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-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-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},"experimental":{"modes":{"fast":{"cost":{"input":30,"output":150,"cache_read":3,"cache_write":37.5},"provider":{"body":{"speed":"fast"},"headers":{"anthropic-beta":"fast-mode-2026-02-01"}}}}}},"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-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-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}}}},"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-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-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-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}},"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-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}},"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}},"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}}}},"hpc-ai":{"id":"hpc-ai","env":["HPC_AI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.hpc-ai.com/inference/v1","name":"HPC-AI","doc":"https://www.hpc-ai.com/doc/docs/quickstart/","models":{"zai-org/glm-5.1":{"id":"zai-org/glm-5.1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.66,"output":2,"cache_read":0.12},"limit":{"context":202000,"output":202000}},"minimax/minimax-m2.5":{"id":"minimax/minimax-m2.5","name":"MiniMax M2.5","family":"minimax-m2.5","attachment":false,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2026-02-12","last_updated":"2026-03-25","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.56,"cache_read":0.014},"limit":{"context":1000000,"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":{"field":"reasoning_content"},"structured_output":true,"temperature":false,"knowledge":"2025-01-01","release_date":"2026-01-01","last_updated":"2026-03-25","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.21,"output":1,"cache_read":0.03},"limit":{"context":262144,"output":262144}}}},"gitlab":{"id":"gitlab","env":["GITLAB_TOKEN"],"npm":"gitlab-ai-provider","name":"GitLab Duo","doc":"https://docs.gitlab.com/user/duo_agent_platform/","models":{"duo-chat-gpt-5-4-nano":{"id":"duo-chat-gpt-5-4-nano","name":"Agentic Chat (GPT-5.4 Nano)","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","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-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-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-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-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-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}},"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-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-gpt-5-4":{"id":"duo-chat-gpt-5-4","name":"Agentic Chat (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":0,"output":0},"limit":{"context":1050000,"input":922000,"output":128000}},"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-3-codex":{"id":"duo-chat-gpt-5-3-codex","name":"Agentic Chat (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":0,"output":0},"limit":{"context":400000,"input":272000,"output":128000}},"duo-chat-gpt-5-4-mini":{"id":"duo-chat-gpt-5-4-mini","name":"Agentic Chat (GPT-5.4 Mini)","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":400000,"input":272000,"output":128000}},"duo-chat-opus-4-7":{"id":"duo-chat-opus-4-7","name":"Agentic Chat (Claude Opus 4.7)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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-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-05-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}}}},"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.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":131072}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"output":131072}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"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":262144,"output":65536}}}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"moonshotai/chat-completion/models/Kimi-K2_6":{"id":"moonshotai/chat-completion/models/Kimi-K2_6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4},"limit":{"context":262144,"output":262144}}}},"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":{"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":{"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.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131072}},"MiniMax-M2.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"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}}}},"regolo-ai":{"id":"regolo-ai","env":["REGOLO_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.regolo.ai/v1","name":"Regolo AI","doc":"https://docs.regolo.ai/","models":{"mistral-small3.2":{"id":"mistral-small3.2","name":"Mistral Small 3.2","family":"mistral-small","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-01-31","last_updated":"2025-01-31","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":2.2},"limit":{"context":120000,"output":120000}},"qwen3-embedding-8b":{"id":"qwen3-embedding-8b","name":"Qwen3-Embedding-8B","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":32768,"output":8192}},"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,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":2.7},"limit":{"context":128000,"output":16384}},"qwen3-reranker-4b":{"id":"qwen3-reranker-4b","name":"Qwen3-Reranker-4B","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.12,"output":0.12},"limit":{"context":32768,"output":8192}},"mistral-small-4-119b":{"id":"mistral-small-4-119b","name":"Mistral Small 4 119B","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-15","last_updated":"2026-03-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":3},"limit":{"context":256000,"output":16384}},"qwen3.5-122b":{"id":"qwen3.5-122b","name":"Qwen3.5-122B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.9,"output":3.6},"limit":{"context":262144,"output":16384}},"qwen-image":{"id":"qwen-image","name":"Qwen-Image","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-01","last_updated":"2026-03-01","modalities":{"input":["text"],"output":["image"]},"open_weights":false,"cost":{"input":0.5,"output":2},"limit":{"context":8192,"output":4096}},"qwen3-coder-next":{"id":"qwen3-coder-next","name":"Qwen3-Coder-Next","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-01","last_updated":"2026-03-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2},"limit":{"context":262144,"output":16384}},"minimax-m2.5":{"id":"minimax-m2.5","name":"MiniMax 2.5","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-10","last_updated":"2026-03-10","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":3.5},"limit":{"context":190000,"output":64000}},"gpt-oss-20b":{"id":"gpt-oss-20b","name":"GPT-OSS-20B","family":"gpt-oss","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-01","last_updated":"2026-03-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":1.8},"limit":{"context":128000,"output":16384}},"qwen3.5-9b":{"id":"qwen3.5-9b","name":"Qwen3.5-9B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":262144,"output":8192}},"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":false,"cost":{"input":1,"output":4.2},"limit":{"context":128000,"output":16384}},"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,"release_date":"2025-04-07","last_updated":"2025-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.05,"output":0.25},"limit":{"context":120000,"output":120000}}}},"xiaomi-token-plan-ams":{"id":"xiaomi-token-plan-ams","env":["XIAOMI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://token-plan-ams.xiaomimimo.com/v1","name":"Xiaomi Token Plan (Europe)","doc":"https://platform.xiaomimimo.com/#/docs","models":{"mimo-v2-tts":{"id":"mimo-v2-tts","name":"MiMo-V2-TTS","family":"mimo","attachment":false,"reasoning":false,"tool_call":false,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["audio"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":8192,"output":16384}},"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,"output":0,"cache_read":0},"limit":{"context":262144,"output":65536}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262144,"output":131072}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}}}},"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-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":22,"cache_read":1.2,"cache_write":0},"limit":{"context":200000,"output":131072}},"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-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":24,"cache_read":1.3,"cache_write":0},"limit":{"context":200000,"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.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.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.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.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.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.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}}}},"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}}}}}
diff --git a/packages/opencode/src/provider/models.ts b/packages/core/src/models.ts
similarity index 89%
rename from packages/opencode/src/provider/models.ts
rename to packages/core/src/models.ts
index 8d88e0fce..4ee17b8e2 100644
--- a/packages/opencode/src/provider/models.ts
+++ b/packages/core/src/models.ts
@@ -1,15 +1,17 @@
-import { Global } from "@opencode-ai/core/global"
 import path from "path"
 import { Context, Duration, Effect, Layer, Option, Schedule, Schema } from "effect"
 import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
-import { Installation } from "../installation"
-import { Flag } from "@opencode-ai/core/flag/flag"
-import { Flock } from "@opencode-ai/core/util/flock"
-import { Hash } from "@opencode-ai/core/util/hash"
-import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import { withTransientReadRetry } from "@/util/effect-http-client"
-import { CatalogModelStatus } from "./model-status"
-import { RuntimeFlags } from "@/effect/runtime-flags"
+import { Global } from "./global"
+import { Flag } from "./flag/flag"
+import { Flock } from "./util/flock"
+import { Hash } from "./util/hash"
+import { AppFileSystem } from "./filesystem"
+import { InstallationChannel, InstallationVersion } from "./installation/version"
+
+export const CatalogModelStatus = Schema.Literals(["alpha", "beta", "deprecated"])
+export type CatalogModelStatus = typeof CatalogModelStatus.Type
+
+const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}`
 
 const CostTier = Schema.Struct({
   input: Schema.Finite,
@@ -110,14 +112,21 @@ export interface Interface {
 
 export class Service extends Context.Service()("@opencode/ModelsDev") {}
 
-type Requirements = AppFileSystem.Service | HttpClient.HttpClient | RuntimeFlags.Service
+type Requirements = AppFileSystem.Service | HttpClient.HttpClient
 
 export const layer: Layer.Layer = Layer.effect(
   Service,
   Effect.gen(function* () {
     const fs = yield* AppFileSystem.Service
-    const http = HttpClient.filterStatusOk(withTransientReadRetry(yield* HttpClient.HttpClient))
-    const flags = yield* RuntimeFlags.Service
+    const http = HttpClient.filterStatusOk(
+      (yield* HttpClient.HttpClient).pipe(
+        HttpClient.retryTransient({
+          retryOn: "errors-and-responses",
+          times: 2,
+          schedule: Schedule.exponential(200).pipe(Schedule.jittered),
+        }),
+      ),
+    )
 
     const source = Flag.OPENCODE_MODELS_URL || "https://models.dev"
     const filepath = path.join(
@@ -136,7 +145,7 @@ export const layer: Layer.Layer = Layer.effect(
 
     const fetchApi = Effect.fn("ModelsDev.fetchApi")(function* () {
       return yield* HttpClientRequest.get(`${source}/api.json`).pipe(
-        HttpClientRequest.setHeader("User-Agent", Installation.userAgent(flags.client)),
+        HttpClientRequest.setHeader("User-Agent", USER_AGENT),
         http.execute,
         Effect.flatMap((res) => res.text),
         Effect.timeout("10 seconds"),
@@ -212,7 +221,6 @@ export const layer: Layer.Layer = Layer.effect(
 export const defaultLayer: Layer.Layer = layer.pipe(
   Layer.provide(FetchHttpClient.layer),
   Layer.provide(AppFileSystem.defaultLayer),
-  Layer.provide(RuntimeFlags.defaultLayer),
 )
 
 export * as ModelsDev from "./models"
diff --git a/packages/core/src/plugin/boot.ts b/packages/core/src/plugin/boot.ts
new file mode 100644
index 000000000..d3ea5195c
--- /dev/null
+++ b/packages/core/src/plugin/boot.ts
@@ -0,0 +1,66 @@
+export * as PluginBoot from "./boot"
+
+import { Context, Deferred, Effect, Layer } from "effect"
+import { AuthV2 } from "../auth"
+import { Catalog } from "../catalog"
+import { Npm } from "../npm"
+import { PluginV2 } from "../plugin"
+import { AuthPlugin } from "./auth"
+import { EnvPlugin } from "./env"
+import { ModelsDevPlugin } from "./models-dev"
+import { ProviderPlugins } from "./provider"
+
+type Plugin = {
+  id: PluginV2.ID
+  effect: Effect.Effect
+}
+
+export interface Interface {
+  readonly wait: () => Effect.Effect
+}
+
+export class Service extends Context.Service()("@opencode/v2/PluginBoot") {}
+
+export const layer: Layer.Layer = Layer.effect(
+  Service,
+  Effect.gen(function* () {
+    const catalog = yield* Catalog.Service
+    const plugin = yield* PluginV2.Service
+    const auth = yield* AuthV2.Service
+    const npm = yield* Npm.Service
+    const done = yield* Deferred.make()
+
+    const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) {
+      yield* plugin.add({
+        id: input.id,
+        effect: input.effect.pipe(
+          Effect.provideService(Catalog.Service, catalog),
+          Effect.provideService(AuthV2.Service, auth),
+          Effect.provideService(Npm.Service, npm),
+        ),
+      })
+    })
+
+    const boot = Effect.gen(function* () {
+      yield* add(EnvPlugin)
+      yield* add(AuthPlugin)
+      for (const item of ProviderPlugins) {
+        yield* add(item)
+      }
+      yield* add(ModelsDevPlugin)
+    }).pipe(Effect.withSpan("PluginBoot.boot"))
+
+    yield* boot.pipe(Effect.exit, Effect.flatMap((exit) => Deferred.done(done, exit)), Effect.forkScoped)
+
+    return Service.of({
+      wait: () => Deferred.await(done),
+    })
+  }),
+)
+
+export const defaultLayer = layer.pipe(
+  Layer.provide(Catalog.defaultLayer),
+  Layer.provide(PluginV2.defaultLayer),
+  Layer.provide(Layer.orDie(AuthV2.defaultLayer)),
+  Layer.provide(Npm.defaultLayer),
+)
diff --git a/packages/opencode/src/v2/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts
similarity index 92%
rename from packages/opencode/src/v2/plugin/models-dev.ts
rename to packages/core/src/plugin/models-dev.ts
index 7c0e902c7..e67c2e75a 100644
--- a/packages/opencode/src/v2/plugin/models-dev.ts
+++ b/packages/core/src/plugin/models-dev.ts
@@ -1,9 +1,9 @@
 import { DateTime, Effect } from "effect"
-import { Catalog } from "@opencode-ai/core/catalog"
-import { ModelV2 } from "@opencode-ai/core/model"
-import { ProviderV2 } from "@opencode-ai/core/provider"
-import { ModelsDev } from "@/provider/models"
-import { PluginV2 } from "@opencode-ai/core/plugin"
+import { Catalog } from "../catalog"
+import { ModelV2 } from "../model"
+import { ModelsDev } from "../models"
+import { PluginV2 } from "../plugin"
+import { ProviderV2 } from "../provider"
 
 function released(date: string) {
   const time = Date.parse(date)
diff --git a/packages/core/test/v2/catalog.test.ts b/packages/core/test/catalog.test.ts
similarity index 96%
rename from packages/core/test/v2/catalog.test.ts
rename to packages/core/test/catalog.test.ts
index cba3405bc..c5ac2c06c 100644
--- a/packages/core/test/v2/catalog.test.ts
+++ b/packages/core/test/catalog.test.ts
@@ -1,12 +1,14 @@
 import { describe, expect } from "bun:test"
 import { DateTime, Effect, Layer, Option } from "effect"
 import { Catalog } from "@opencode-ai/core/catalog"
+import { Instance } from "@opencode-ai/core/instance"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { ProviderV2 } from "@opencode-ai/core/provider"
-import { testEffect } from "../lib/effect"
+import { testEffect } from "./lib/effect"
 
-const it = testEffect(Catalog.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer)))
+const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" }))
+const it = testEffect(Catalog.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer), Layer.provide(instanceLayer)))
 
 describe("CatalogV2", () => {
   it.effect("normalizes provider baseURL into endpoint url", () =>
diff --git a/packages/opencode/test/provider/models.test.ts b/packages/core/test/models.test.ts
similarity index 94%
rename from packages/opencode/test/provider/models.test.ts
rename to packages/core/test/models.test.ts
index c0dbd5caa..0ade327a5 100644
--- a/packages/opencode/test/provider/models.test.ts
+++ b/packages/core/test/models.test.ts
@@ -4,11 +4,10 @@ import { HttpClient, HttpClientResponse } from "effect/unstable/http"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Global } from "@opencode-ai/core/global"
-import { ModelsDev } from "../../src/provider/models"
-import { it } from "../lib/effect"
+import { ModelsDev } from "@opencode-ai/core/models"
+import { it } from "./lib/effect"
 import { rm, writeFile, utimes, mkdir } from "fs/promises"
 import path from "path"
-import { RuntimeFlags } from "@/effect/runtime-flags"
 
 // test/preload.ts pins OPENCODE_MODELS_PATH to a fixture so other tests can
 // resolve providers without network. These tests need to drive the on-disk
@@ -93,7 +92,6 @@ const buildLayer = (state: Ref.Ref) =>
   Layer.fresh(ModelsDev.layer).pipe(
     Layer.provide(Layer.succeed(HttpClient.HttpClient, makeMockClient(state))),
     Layer.provide(AppFileSystem.defaultLayer),
-    Layer.provide(RuntimeFlags.layer({ client: "test-client" })),
   )
 
 const writeCache = (data: object, mtimeMs?: number) =>
@@ -138,14 +136,14 @@ describe("ModelsDev Service", () => {
     }),
   )
 
-  it.live("get() returns {} when disk empty and fetch disabled", () =>
+  it.live("get() returns bundled snapshot when disk empty and fetch disabled", () =>
     Effect.gen(function* () {
       const state = yield* Ref.make(initialState)
       const result = yield* provided(
         state,
         ModelsDev.Service.use((s) => s.get()),
       )
-      expect(result).toEqual({})
+      expect(Object.keys(result).length).toBeGreaterThan(0)
       const final = yield* Ref.get(state)
       expect(final.calls).toEqual([])
     }),
@@ -207,7 +205,7 @@ describe("ModelsDev Service", () => {
       const final = yield* Ref.get(state)
       expect(final.calls.length).toBe(1)
       expect(final.calls[0].url).toContain("/api.json")
-      expect(final.calls[0].userAgent).toContain("/test-client")
+      expect(final.calls[0].userAgent).toContain("/cli")
     }),
   )
 
@@ -257,7 +255,7 @@ describe("ModelsDev Service", () => {
         }),
       )
       expect(result).toEqual(fixture)
-      // withTransientReadRetry retries 5xx, so calls may be > 1.
+      // retryTransient retries 5xx, so calls may be > 1.
       const final = yield* Ref.get(state)
       expect(final.calls.length).toBeGreaterThanOrEqual(1)
     }),
diff --git a/packages/core/test/v2/plugin/fixtures/provider-factory.ts b/packages/core/test/plugin/fixtures/provider-factory.ts
similarity index 100%
rename from packages/core/test/v2/plugin/fixtures/provider-factory.ts
rename to packages/core/test/plugin/fixtures/provider-factory.ts
diff --git a/packages/core/test/v2/plugin/provider-alibaba.test.ts b/packages/core/test/plugin/provider-alibaba.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-alibaba.test.ts
rename to packages/core/test/plugin/provider-alibaba.test.ts
diff --git a/packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts b/packages/core/test/plugin/provider-amazon-bedrock.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-amazon-bedrock.test.ts
rename to packages/core/test/plugin/provider-amazon-bedrock.test.ts
diff --git a/packages/core/test/v2/plugin/provider-anthropic.test.ts b/packages/core/test/plugin/provider-anthropic.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-anthropic.test.ts
rename to packages/core/test/plugin/provider-anthropic.test.ts
diff --git a/packages/core/test/v2/plugin/provider-azure-cognitive-services.test.ts b/packages/core/test/plugin/provider-azure-cognitive-services.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-azure-cognitive-services.test.ts
rename to packages/core/test/plugin/provider-azure-cognitive-services.test.ts
diff --git a/packages/core/test/v2/plugin/provider-azure.test.ts b/packages/core/test/plugin/provider-azure.test.ts
similarity index 99%
rename from packages/core/test/v2/plugin/provider-azure.test.ts
rename to packages/core/test/plugin/provider-azure.test.ts
index 12d8363e7..5121b1eec 100644
--- a/packages/core/test/v2/plugin/provider-azure.test.ts
+++ b/packages/core/test/plugin/provider-azure.test.ts
@@ -4,7 +4,7 @@ import { AuthV2 } from "@opencode-ai/core/auth"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { AuthPlugin } from "@opencode-ai/core/plugin/auth"
 import { AzurePlugin } from "@opencode-ai/core/plugin/provider/azure"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { fakeSelectorSdk, it, model, npmLayer, provider, withEnv } from "./provider-helper"
 
 const itWithAuth = testEffect(Layer.mergeAll(PluginV2.defaultLayer, AuthV2.defaultLayer, npmLayer))
diff --git a/packages/core/test/v2/plugin/provider-cerebras.test.ts b/packages/core/test/plugin/provider-cerebras.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-cerebras.test.ts
rename to packages/core/test/plugin/provider-cerebras.test.ts
diff --git a/packages/core/test/v2/plugin/provider-cloudflare-ai-gateway.test.ts b/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-cloudflare-ai-gateway.test.ts
rename to packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts
diff --git a/packages/core/test/v2/plugin/provider-cloudflare-workers-ai.test.ts b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts
similarity index 99%
rename from packages/core/test/v2/plugin/provider-cloudflare-workers-ai.test.ts
rename to packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts
index 3aed2a17b..10aba171c 100644
--- a/packages/core/test/v2/plugin/provider-cloudflare-workers-ai.test.ts
+++ b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts
@@ -5,7 +5,7 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { AuthPlugin } from "@opencode-ai/core/plugin/auth"
 import { CloudflareWorkersAIPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-workers-ai"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { fakeSelectorSdk, it, model, npmLayer, provider, withEnv } from "./provider-helper"
 
 const itWithAuth = testEffect(Layer.mergeAll(PluginV2.defaultLayer, AuthV2.defaultLayer, npmLayer))
diff --git a/packages/core/test/v2/plugin/provider-cohere.test.ts b/packages/core/test/plugin/provider-cohere.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-cohere.test.ts
rename to packages/core/test/plugin/provider-cohere.test.ts
diff --git a/packages/core/test/v2/plugin/provider-deepinfra.test.ts b/packages/core/test/plugin/provider-deepinfra.test.ts
similarity index 98%
rename from packages/core/test/v2/plugin/provider-deepinfra.test.ts
rename to packages/core/test/plugin/provider-deepinfra.test.ts
index 1195b8c18..9a9cb861e 100644
--- a/packages/core/test/v2/plugin/provider-deepinfra.test.ts
+++ b/packages/core/test/plugin/provider-deepinfra.test.ts
@@ -3,7 +3,7 @@ import { Effect, Layer } from "effect"
 import { AISDK } from "@opencode-ai/core/aisdk"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { DeepInfraPlugin } from "@opencode-ai/core/plugin/provider/deepinfra"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { it, model } from "./provider-helper"
 
 const itAISDK = testEffect(Layer.provideMerge(AISDK.layer, PluginV2.defaultLayer))
diff --git a/packages/core/test/v2/plugin/provider-dynamic.test.ts b/packages/core/test/plugin/provider-dynamic.test.ts
similarity index 99%
rename from packages/core/test/v2/plugin/provider-dynamic.test.ts
rename to packages/core/test/plugin/provider-dynamic.test.ts
index cca331b11..c15568eeb 100644
--- a/packages/core/test/v2/plugin/provider-dynamic.test.ts
+++ b/packages/core/test/plugin/provider-dynamic.test.ts
@@ -9,7 +9,7 @@ import { AISDK } from "@opencode-ai/core/aisdk"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { DynamicProviderPlugin } from "@opencode-ai/core/plugin/provider/dynamic"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { fixtureProvider, it, model, npmLayer } from "./provider-helper"
 
 const fixtureProviderPath = fileURLToPath(fixtureProvider)
diff --git a/packages/core/test/v2/plugin/provider-gateway.test.ts b/packages/core/test/plugin/provider-gateway.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-gateway.test.ts
rename to packages/core/test/plugin/provider-gateway.test.ts
diff --git a/packages/core/test/plugin/provider-github-copilot.test.ts b/packages/core/test/plugin/provider-github-copilot.test.ts
index c825f7b8e..e2bd899ff 100644
--- a/packages/core/test/plugin/provider-github-copilot.test.ts
+++ b/packages/core/test/plugin/provider-github-copilot.test.ts
@@ -3,7 +3,7 @@ import { Effect } from "effect"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { GithubCopilotPlugin } from "@opencode-ai/core/plugin/provider/github-copilot"
-import { fakeSelectorSdk, it, model } from "../v2/plugin/provider-helper"
+import { fakeSelectorSdk, it, model } from "./provider-helper"
 
 describe("GithubCopilotPlugin", () => {
   it.effect("creates the bundled Copilot SDK for the GitHub Copilot package", () =>
diff --git a/packages/core/test/v2/plugin/provider-gitlab.test.ts b/packages/core/test/plugin/provider-gitlab.test.ts
similarity index 99%
rename from packages/core/test/v2/plugin/provider-gitlab.test.ts
rename to packages/core/test/plugin/provider-gitlab.test.ts
index 0b71310e0..56a22649a 100644
--- a/packages/core/test/v2/plugin/provider-gitlab.test.ts
+++ b/packages/core/test/plugin/provider-gitlab.test.ts
@@ -4,7 +4,7 @@ import { AuthV2 } from "@opencode-ai/core/auth"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { AuthPlugin } from "@opencode-ai/core/plugin/auth"
 import { GitLabPlugin } from "@opencode-ai/core/plugin/provider/gitlab"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { it, model, npmLayer, provider, withEnv } from "./provider-helper"
 
 const gitlabSDKOptions: Record[] = []
diff --git a/packages/core/test/v2/plugin/provider-google-vertex-anthropic.test.ts b/packages/core/test/plugin/provider-google-vertex-anthropic.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-google-vertex-anthropic.test.ts
rename to packages/core/test/plugin/provider-google-vertex-anthropic.test.ts
diff --git a/packages/core/test/v2/plugin/provider-google-vertex.test.ts b/packages/core/test/plugin/provider-google-vertex.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-google-vertex.test.ts
rename to packages/core/test/plugin/provider-google-vertex.test.ts
diff --git a/packages/core/test/v2/plugin/provider-google.test.ts b/packages/core/test/plugin/provider-google.test.ts
similarity index 98%
rename from packages/core/test/v2/plugin/provider-google.test.ts
rename to packages/core/test/plugin/provider-google.test.ts
index ee33b980b..fdb7bf75e 100644
--- a/packages/core/test/v2/plugin/provider-google.test.ts
+++ b/packages/core/test/plugin/provider-google.test.ts
@@ -4,7 +4,7 @@ import { AISDK } from "@opencode-ai/core/aisdk"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { GooglePlugin } from "@opencode-ai/core/plugin/provider/google"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { it, model } from "./provider-helper"
 
 const itWithAISDK = testEffect(AISDK.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer)))
diff --git a/packages/core/test/v2/plugin/provider-groq.test.ts b/packages/core/test/plugin/provider-groq.test.ts
similarity index 98%
rename from packages/core/test/v2/plugin/provider-groq.test.ts
rename to packages/core/test/plugin/provider-groq.test.ts
index 14c10b651..579d70da5 100644
--- a/packages/core/test/v2/plugin/provider-groq.test.ts
+++ b/packages/core/test/plugin/provider-groq.test.ts
@@ -6,7 +6,7 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { GroqPlugin } from "@opencode-ai/core/plugin/provider/groq"
 import { it, model } from "./provider-helper"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 
 const aisdkIt = testEffect(AISDK.layer.pipe(Layer.provideMerge(PluginV2.defaultLayer)))
 
diff --git a/packages/core/test/v2/plugin/provider-helper.ts b/packages/core/test/plugin/provider-helper.ts
similarity index 98%
rename from packages/core/test/v2/plugin/provider-helper.ts
rename to packages/core/test/plugin/provider-helper.ts
index 84a3044bf..0d67075ac 100644
--- a/packages/core/test/v2/plugin/provider-helper.ts
+++ b/packages/core/test/plugin/provider-helper.ts
@@ -5,7 +5,7 @@ import { Effect, Layer, Option } from "effect"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { ProviderV2 } from "@opencode-ai/core/provider"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 
 export const fixtureProvider = new URL("./fixtures/provider-factory.ts", import.meta.url).href
 
diff --git a/packages/core/test/v2/plugin/provider-kilo.test.ts b/packages/core/test/plugin/provider-kilo.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-kilo.test.ts
rename to packages/core/test/plugin/provider-kilo.test.ts
diff --git a/packages/core/test/v2/plugin/provider-llmgateway.test.ts b/packages/core/test/plugin/provider-llmgateway.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-llmgateway.test.ts
rename to packages/core/test/plugin/provider-llmgateway.test.ts
diff --git a/packages/core/test/v2/plugin/provider-mistral.test.ts b/packages/core/test/plugin/provider-mistral.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-mistral.test.ts
rename to packages/core/test/plugin/provider-mistral.test.ts
diff --git a/packages/core/test/v2/plugin/provider-nvidia.test.ts b/packages/core/test/plugin/provider-nvidia.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-nvidia.test.ts
rename to packages/core/test/plugin/provider-nvidia.test.ts
diff --git a/packages/core/test/v2/plugin/provider-openai-compatible.test.ts b/packages/core/test/plugin/provider-openai-compatible.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-openai-compatible.test.ts
rename to packages/core/test/plugin/provider-openai-compatible.test.ts
diff --git a/packages/core/test/v2/plugin/provider-openai.test.ts b/packages/core/test/plugin/provider-openai.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-openai.test.ts
rename to packages/core/test/plugin/provider-openai.test.ts
diff --git a/packages/core/test/v2/plugin/provider-opencode.test.ts b/packages/core/test/plugin/provider-opencode.test.ts
similarity index 96%
rename from packages/core/test/v2/plugin/provider-opencode.test.ts
rename to packages/core/test/plugin/provider-opencode.test.ts
index f080776d4..8271a4a5c 100644
--- a/packages/core/test/v2/plugin/provider-opencode.test.ts
+++ b/packages/core/test/plugin/provider-opencode.test.ts
@@ -1,6 +1,7 @@
 import { describe, expect } from "bun:test"
-import { DateTime, Effect, Option } from "effect"
+import { DateTime, Effect, Layer, Option } from "effect"
 import { Catalog } from "@opencode-ai/core/catalog"
+import { Instance } from "@opencode-ai/core/instance"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode"
@@ -8,6 +9,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider"
 import { it, model, provider, withEnv } from "./provider-helper"
 
 const cost = (input: number, output = 0) => [{ input, output, cache: { read: 0, write: 0 } }]
+const instanceLayer = Layer.succeed(Instance.Service, Instance.Service.of({ directory: "test" }))
 
 describe("OpencodePlugin", () => {
   it.effect("uses a public key and cancels paid models without credentials", () =>
@@ -190,6 +192,6 @@ describe("OpencodePlugin", () => {
       const selected = yield* catalog.model.small(providerID)
 
       expect(Option.getOrUndefined(selected)?.id).toBe(ModelV2.ID.make("gpt-5-nano"))
-    }).pipe(Effect.provide(Catalog.defaultLayer)),
+    }).pipe(Effect.provide(Catalog.defaultLayer.pipe(Layer.provide(instanceLayer)))),
   )
 })
diff --git a/packages/core/test/v2/plugin/provider-openrouter.test.ts b/packages/core/test/plugin/provider-openrouter.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-openrouter.test.ts
rename to packages/core/test/plugin/provider-openrouter.test.ts
diff --git a/packages/core/test/v2/plugin/provider-perplexity.test.ts b/packages/core/test/plugin/provider-perplexity.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-perplexity.test.ts
rename to packages/core/test/plugin/provider-perplexity.test.ts
diff --git a/packages/core/test/v2/plugin/provider-sap-ai-core.test.ts b/packages/core/test/plugin/provider-sap-ai-core.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-sap-ai-core.test.ts
rename to packages/core/test/plugin/provider-sap-ai-core.test.ts
diff --git a/packages/core/test/v2/plugin/provider-togetherai.test.ts b/packages/core/test/plugin/provider-togetherai.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-togetherai.test.ts
rename to packages/core/test/plugin/provider-togetherai.test.ts
diff --git a/packages/core/test/v2/plugin/provider-venice.test.ts b/packages/core/test/plugin/provider-venice.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-venice.test.ts
rename to packages/core/test/plugin/provider-venice.test.ts
diff --git a/packages/core/test/v2/plugin/provider-vercel.test.ts b/packages/core/test/plugin/provider-vercel.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-vercel.test.ts
rename to packages/core/test/plugin/provider-vercel.test.ts
diff --git a/packages/core/test/v2/plugin/provider-xai.test.ts b/packages/core/test/plugin/provider-xai.test.ts
similarity index 98%
rename from packages/core/test/v2/plugin/provider-xai.test.ts
rename to packages/core/test/plugin/provider-xai.test.ts
index bb2828ff4..63af32dae 100644
--- a/packages/core/test/v2/plugin/provider-xai.test.ts
+++ b/packages/core/test/plugin/provider-xai.test.ts
@@ -4,7 +4,7 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { PluginV2 } from "@opencode-ai/core/plugin"
 import { XAIPlugin } from "@opencode-ai/core/plugin/provider/xai"
 import { ProviderV2 } from "@opencode-ai/core/provider"
-import { testEffect } from "../../lib/effect"
+import { testEffect } from "../lib/effect"
 import { fakeSelectorSdk } from "./provider-helper"
 
 const it = testEffect(PluginV2.defaultLayer)
diff --git a/packages/core/test/v2/plugin/provider-zenmux.test.ts b/packages/core/test/plugin/provider-zenmux.test.ts
similarity index 100%
rename from packages/core/test/v2/plugin/provider-zenmux.test.ts
rename to packages/core/test/plugin/provider-zenmux.test.ts
diff --git a/packages/opencode/script/generate.ts b/packages/opencode/script/generate.ts
index 52d0cef8d..bd9e7c064 100644
--- a/packages/opencode/script/generate.ts
+++ b/packages/opencode/script/generate.ts
@@ -13,11 +13,11 @@ const modelsData = process.env.MODELS_DEV_API_JSON
   ? await Bun.file(process.env.MODELS_DEV_API_JSON).text()
   : await fetch(`${modelsUrl}/api.json`).then((x) => x.text())
 await Bun.write(
-  path.join(dir, "src/provider/models-snapshot.js"),
+  path.join(dir, "../core/src/models-snapshot.js"),
   `// @ts-nocheck\n// Auto-generated by build.ts - do not edit\nexport const snapshot = ${modelsData}\n`,
 )
 await Bun.write(
-  path.join(dir, "src/provider/models-snapshot.d.ts"),
+  path.join(dir, "../core/src/models-snapshot.d.ts"),
   `// Auto-generated by build.ts - do not edit\nexport declare const snapshot: Record\n`,
 )
 console.log("Generated models-snapshot.js")
diff --git a/packages/opencode/src/cli/cmd/debug/v2.ts b/packages/opencode/src/cli/cmd/debug/v2.ts
index a4e69cc49..507f82cdf 100644
--- a/packages/opencode/src/cli/cmd/debug/v2.ts
+++ b/packages/opencode/src/cli/cmd/debug/v2.ts
@@ -1,22 +1,23 @@
 import { EOL } from "os"
 import { Effect, Layer, Option } from "effect"
 import { Catalog } from "@opencode-ai/core/catalog"
+import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
+import { PluginBoot } from "@opencode-ai/core/plugin/boot"
 import { effectCmd } from "../../effect-cmd"
-import { PluginBoot } from "@/v2/plugin-boot"
 
-const layer = Catalog.defaultLayer.pipe(Layer.provide(PluginBoot.defaultLayer))
+const Runtime = Layer.mergeAll(InstanceServiceMap.layer)
 
 export const V2Command = effectCmd({
   command: "v2",
   describe: "debug v2 catalog and built-in plugins",
   instance: false,
-  handler: Effect.fn("Cli.debug.v2")(function* () {
-    const result = yield* Effect.gen(function* () {
+  handler: Effect.fn("Cli.debug.v2")(
+    function* () {
+      yield* PluginBoot.Service.use((service) => service.wait())
       const catalog = yield* Catalog.Service
-
       const providers = (yield* catalog.provider.available()).sort((a, b) => a.id.localeCompare(b.id))
       const all = (yield* catalog.provider.all()).sort((a, b) => a.id.localeCompare(b.id))
-      return {
+      const result = {
         providers,
         default: catalog.model
           .default()
@@ -33,8 +34,13 @@ export const V2Command = effectCmd({
           ),
         ),
       }
-    }).pipe(Effect.provide(layer), Effect.orDie)
-
-    process.stdout.write(JSON.stringify(result, null, 2) + EOL)
-  }),
+      process.stdout.write(JSON.stringify(result, null, 2) + EOL)
+    },
+    Effect.provide(
+      InstanceServiceMap.get({
+        directory: process.cwd(),
+      }),
+    ),
+    Effect.provide(Runtime),
+  ),
 })
diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts
index a6754ec2d..e501f0903 100644
--- a/packages/opencode/src/cli/cmd/github.ts
+++ b/packages/opencode/src/cli/cmd/github.ts
@@ -19,7 +19,7 @@ import type {
 import { UI } from "../ui"
 import { cmd } from "./cmd"
 import { effectCmd } from "../effect-cmd"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { InstanceRef } from "@/effect/instance-ref"
 import { SessionShare } from "@/share/session"
 import { Session } from "@/session/session"
diff --git a/packages/opencode/src/cli/cmd/models.ts b/packages/opencode/src/cli/cmd/models.ts
index 183b1816d..08203ba21 100644
--- a/packages/opencode/src/cli/cmd/models.ts
+++ b/packages/opencode/src/cli/cmd/models.ts
@@ -2,7 +2,7 @@ import { EOL } from "os"
 import { Effect } from "effect"
 import { Provider } from "@/provider/provider"
 import { ProviderID } from "../../provider/schema"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { effectCmd, fail } from "../effect-cmd"
 import { UI } from "../ui"
 
diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts
index 426ea89fc..25f1bf968 100644
--- a/packages/opencode/src/cli/cmd/providers.ts
+++ b/packages/opencode/src/cli/cmd/providers.ts
@@ -3,7 +3,7 @@ import { cmd } from "./cmd"
 import { CliError, effectCmd, fail } from "../effect-cmd"
 import { UI } from "../ui"
 import * as Prompt from "../effect/prompt"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 
 import { map, pipe, sortBy, values } from "remeda"
 import path from "path"
diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts
index b0efab1ae..d5e9f529b 100644
--- a/packages/opencode/src/effect/app-runtime.ts
+++ b/packages/opencode/src/effect/app-runtime.ts
@@ -14,7 +14,7 @@ import { FileWatcher } from "@/file/watcher"
 import { Storage } from "@/storage/storage"
 import { Snapshot } from "@/snapshot"
 import { Plugin } from "@/plugin"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 import { ProviderAuth } from "@/provider/auth"
 import { Agent } from "@/agent/agent"
diff --git a/packages/opencode/src/provider/model-status.ts b/packages/opencode/src/provider/model-status.ts
index 468b59ce3..7e1ca56e2 100644
--- a/packages/opencode/src/provider/model-status.ts
+++ b/packages/opencode/src/provider/model-status.ts
@@ -1,7 +1,6 @@
 import { Schema } from "effect"
 
-export const CatalogModelStatus = Schema.Literals(["alpha", "beta", "deprecated"])
-export type CatalogModelStatus = typeof CatalogModelStatus.Type
+export { CatalogModelStatus } from "@opencode-ai/core/models"
 
 export const ModelStatus = Schema.Literals(["alpha", "beta", "deprecated", "active"])
 export type ModelStatus = typeof ModelStatus.Type
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index d2c6eafd4..09dc40bc4 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -8,7 +8,7 @@ import { Npm } from "@opencode-ai/core/npm"
 import { Hash } from "@opencode-ai/core/util/hash"
 import { Plugin } from "../plugin"
 import { type LanguageModelV3 } from "@ai-sdk/provider"
-import * as ModelsDev from "./models"
+import * as ModelsDev from "@opencode-ai/core/models"
 import { Auth } from "../auth"
 import { Env } from "../env"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts
index 1021d83f0..d7b0f0e08 100644
--- a/packages/opencode/src/provider/transform.ts
+++ b/packages/opencode/src/provider/transform.ts
@@ -2,7 +2,7 @@ import type { ModelMessage, ToolResultPart } from "ai"
 import { mergeDeep, unique } from "remeda"
 import type { JSONSchema7 } from "@ai-sdk/provider"
 import type * as Provider from "./provider"
-import type * as ModelsDev from "./models"
+import type * as ModelsDev from "@opencode-ai/core/models"
 import { iife } from "@/util/iife"
 import { Flag } from "@opencode-ai/core/flag/flag"
 
diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/instance.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/instance.ts
new file mode 100644
index 000000000..56ab0564b
--- /dev/null
+++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/instance.ts
@@ -0,0 +1,59 @@
+import { Catalog } from "@opencode-ai/core/catalog"
+import { Instance } from "@opencode-ai/core/instance"
+import { InstanceServiceMap } from "@opencode-ai/core/instance-layer"
+import { PluginBoot } from "@opencode-ai/core/plugin/boot"
+import { Effect, Layer, Schema } from "effect"
+import { HttpServerRequest } from "effect/unstable/http"
+import { HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi"
+
+export const InstanceQuery = Schema.Struct({
+  instance: Schema.optional(
+    Schema.Struct({
+      directory: Schema.optional(Schema.String),
+      workspace: Schema.optional(Schema.String),
+    }),
+  ),
+}).annotate({ identifier: "V2InstanceQuery" })
+
+export const instanceQueryOpenApi = OpenApi.annotations({
+  transform: (operation) => {
+    const parameters = operation.parameters
+    if (!Array.isArray(parameters)) return operation
+    return {
+      ...operation,
+      parameters: parameters.map((parameter) =>
+        parameter?.name === "instance" && parameter?.in === "query"
+          ? { ...parameter, style: "deepObject", explode: true }
+          : parameter,
+      ),
+    }
+  },
+})
+
+export class V2InstanceMiddleware extends HttpApiMiddleware.Service<
+  V2InstanceMiddleware,
+  {
+    provides: Catalog.Service | PluginBoot.Service
+  }
+>()("@opencode/ExperimentalHttpApiV2Instance") {}
+
+function ref(request: HttpServerRequest.HttpServerRequest): Instance.Ref {
+  const query = new URL(request.url, "http://localhost").searchParams
+  return {
+    directory: query.get("instance[directory]") || request.headers["x-opencode-directory"] || process.cwd(),
+    workspaceID: query.get("instance[workspace]") || request.headers["x-opencode-workspace"],
+  }
+}
+
+export const layer = Layer.effect(
+  V2InstanceMiddleware,
+  Effect.gen(function* () {
+    const instances = yield* InstanceServiceMap
+    return V2InstanceMiddleware.of((effect) =>
+      Effect.gen(function* () {
+        const request = yield* HttpServerRequest.HttpServerRequest
+        return yield* effect.pipe(Effect.provide(instances.get(ref(request))))
+      }),
+    )
+  }),
+).pipe(Layer.provide(InstanceServiceMap.layer))
diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts
index 35e7aeb85..cd7aed48d 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts
@@ -2,12 +2,14 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { Schema } from "effect"
 import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
 import { Authorization } from "../../middleware/authorization"
+import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
 
 export const ModelGroup = HttpApiGroup.make("v2.model")
   .add(
     HttpApiEndpoint.get("models", "/api/model", {
+      query: InstanceQuery,
       success: Schema.Array(ModelV2.Info),
-    }).annotateMerge(
+    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
       OpenApi.annotations({
         identifier: "v2.model.list",
         summary: "List v2 models",
@@ -21,4 +23,5 @@ export const ModelGroup = HttpApiGroup.make("v2.model")
       description: "Experimental v2 model routes.",
     }),
   )
+  .middleware(V2InstanceMiddleware)
   .middleware(Authorization)
diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts
index 6d9210088..a44335d4c 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts
@@ -3,12 +3,14 @@ import { Schema } from "effect"
 import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
 import { ApiNotFoundError } from "../../errors"
 import { Authorization } from "../../middleware/authorization"
+import { InstanceQuery, instanceQueryOpenApi, V2InstanceMiddleware } from "./instance"
 
 export const ProviderGroup = HttpApiGroup.make("v2.provider")
   .add(
     HttpApiEndpoint.get("providers", "/api/provider", {
+      query: InstanceQuery,
       success: Schema.Array(ProviderV2.Info),
-    }).annotateMerge(
+    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
       OpenApi.annotations({
         identifier: "v2.provider.list",
         summary: "List v2 providers",
@@ -19,9 +21,10 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
   .add(
     HttpApiEndpoint.get("provider", "/api/provider/:providerID", {
       params: { providerID: ProviderV2.ID },
+      query: InstanceQuery,
       success: ProviderV2.Info,
       error: ApiNotFoundError,
-    }).annotateMerge(
+    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
       OpenApi.annotations({
         identifier: "v2.provider.get",
         summary: "Get v2 provider",
@@ -35,4 +38,5 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
       description: "Experimental v2 provider routes.",
     }),
   )
+  .middleware(V2InstanceMiddleware)
   .middleware(Authorization)
diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts
index 9da31582a..dba684228 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts
@@ -1,6 +1,6 @@
 import { ProviderAuth } from "@/provider/auth"
 import { Config } from "@/config/config"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 import { ProviderID } from "@/provider/schema"
 import { mapValues } from "remeda"
diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts
index b277e7701..7739001db 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2.ts
@@ -1,12 +1,12 @@
-import { Catalog } from "@opencode-ai/core/catalog"
 import { SessionV2 } from "@/v2/session"
 import { Layer } from "effect"
+import { layer as v2InstanceLayer } from "../groups/v2/instance"
 import { messageHandlers } from "./v2/message"
 import { modelHandlers } from "./v2/model"
 import { providerHandlers } from "./v2/provider"
 import { sessionHandlers } from "./v2/session"
 
 export const v2Handlers = Layer.mergeAll(sessionHandlers, messageHandlers, modelHandlers, providerHandlers).pipe(
-  Layer.provide(Catalog.defaultLayer),
+  Layer.provide(v2InstanceLayer),
   Layer.provide(SessionV2.defaultLayer),
 )
diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts
index 7eb1b310b..9ba4c654a 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/model.ts
@@ -1,12 +1,19 @@
 import { Catalog } from "@opencode-ai/core/catalog"
+import { PluginBoot } from "@opencode-ai/core/plugin/boot"
 import { Effect } from "effect"
 import { HttpApiBuilder } from "effect/unstable/httpapi"
 import { InstanceHttpApi } from "../../api"
 
 export const modelHandlers = HttpApiBuilder.group(InstanceHttpApi, "v2.model", (handlers) =>
   Effect.gen(function* () {
-    const catalog = yield* Catalog.Service
-
-    return handlers.handle("models", () => catalog.model.available())
+    return handlers.handle(
+      "models",
+      Effect.fn(function* () {
+        const catalog = yield* Catalog.Service
+        const pluginBoot = yield* PluginBoot.Service
+        yield* pluginBoot.wait()
+        return yield* catalog.model.available()
+      }),
+    )
   }),
 )
diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts
index c19213f5b..a77cb159c 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/v2/provider.ts
@@ -1,4 +1,5 @@
 import { Catalog } from "@opencode-ai/core/catalog"
+import { PluginBoot } from "@opencode-ai/core/plugin/boot"
 import { Effect } from "effect"
 import { HttpApiBuilder } from "effect/unstable/httpapi"
 import { InstanceHttpApi } from "../../api"
@@ -6,13 +7,22 @@ import { notFound } from "../../errors"
 
 export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "v2.provider", (handlers) =>
   Effect.gen(function* () {
-    const catalog = yield* Catalog.Service
-
     return handlers
-      .handle("providers", () => catalog.provider.available())
+      .handle(
+        "providers",
+        Effect.fn(function* () {
+          const catalog = yield* Catalog.Service
+          const pluginBoot = yield* PluginBoot.Service
+          yield* pluginBoot.wait()
+          return yield* catalog.provider.available()
+        }),
+      )
       .handle(
         "provider",
         Effect.fn(function* (ctx) {
+          const catalog = yield* Catalog.Service
+          const pluginBoot = yield* PluginBoot.Service
+          yield* pluginBoot.wait()
           return yield* catalog.provider
             .get(ctx.params.providerID)
             .pipe(Effect.catchTag("CatalogV2.ProviderNotFound", () => Effect.fail(notFound("Provider not found"))))
diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts
index 7ce21dfad..84feef6b0 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/server.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts
@@ -29,7 +29,7 @@ import { InstanceLayer } from "@/project/instance-layer"
 import { Plugin } from "@/plugin"
 import { Project } from "@/project/project"
 import { ProviderAuth } from "@/provider/auth"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 import { Pty } from "@/pty"
 import { PtyTicket } from "@/pty/ticket"
diff --git a/packages/opencode/src/v2/plugin-boot.ts b/packages/opencode/src/v2/plugin-boot.ts
deleted file mode 100644
index d19872f2d..000000000
--- a/packages/opencode/src/v2/plugin-boot.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export * as PluginBoot from "./plugin-boot"
-
-import { Npm } from "@opencode-ai/core/npm"
-import { Effect, Layer } from "effect"
-import { AuthV2 } from "@opencode-ai/core/auth"
-import { Catalog } from "@opencode-ai/core/catalog"
-import { PluginV2 } from "@opencode-ai/core/plugin"
-import { AuthPlugin } from "@opencode-ai/core/plugin/auth"
-import { EnvPlugin } from "@opencode-ai/core/plugin/env"
-import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
-import { ModelsDevPlugin } from "./plugin/models-dev"
-
-type Plugin = {
-  id: PluginV2.ID
-  effect: Effect.Effect
-}
-
-export const layer = Layer.effectDiscard(
-  Effect.gen(function* () {
-    const catalog = yield* Catalog.Service
-    const plugin = yield* PluginV2.Service
-    const auth = yield* AuthV2.Service
-    const npm = yield* Npm.Service
-
-    const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) {
-      yield* plugin.add({
-        id: input.id,
-        effect: input.effect.pipe(
-          Effect.provideService(Catalog.Service, catalog),
-          Effect.provideService(AuthV2.Service, auth),
-          Effect.provideService(Npm.Service, npm),
-        ),
-      })
-    })
-
-    yield* add(EnvPlugin)
-    yield* add(AuthPlugin)
-    for (const item of ProviderPlugins) {
-      yield* add(item)
-    }
-    yield* add(ModelsDevPlugin)
-  }),
-)
-
-export const defaultLayer = layer.pipe(
-  Layer.provide(Catalog.defaultLayer),
-  Layer.provide(PluginV2.defaultLayer),
-  Layer.provide(Layer.orDie(AuthV2.defaultLayer)),
-  Layer.provide(Npm.defaultLayer),
-)
diff --git a/packages/opencode/test/provider/model-status.test.ts b/packages/opencode/test/provider/model-status.test.ts
index e6fa645e7..96f50a1b5 100644
--- a/packages/opencode/test/provider/model-status.test.ts
+++ b/packages/opencode/test/provider/model-status.test.ts
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
 import { Schema } from "effect"
 import { ConfigProvider } from "@/config/provider"
 import { CatalogModelStatus, ModelStatus } from "@/provider/model-status"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 
 describe("provider model status schemas", () => {
diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts
index da708a59d..269064a60 100644
--- a/packages/opencode/test/provider/provider.test.ts
+++ b/packages/opencode/test/provider/provider.test.ts
@@ -7,7 +7,7 @@ import { Global } from "@opencode-ai/core/global"
 import { Instance } from "../../src/project/instance"
 import { WithInstance } from "../../src/project/with-instance"
 import { Plugin } from "../../src/plugin/index"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { Provider } from "@/provider/provider"
 import { ProviderID, ModelID } from "../../src/provider/schema"
 import { Filesystem } from "@/util/filesystem"
diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts
index 4a6b1e8b7..3a949287e 100644
--- a/packages/opencode/test/session/llm.test.ts
+++ b/packages/opencode/test/session/llm.test.ts
@@ -9,7 +9,7 @@ import { Instance } from "../../src/project/instance"
 import { WithInstance } from "../../src/project/with-instance"
 import { Provider } from "@/provider/provider"
 import { ProviderTransform } from "@/provider/transform"
-import { ModelsDev } from "@/provider/models"
+import { ModelsDev } from "@opencode-ai/core/models"
 import { ProviderID, ModelID } from "../../src/provider/schema"
 import { Filesystem } from "@/util/filesystem"
 import { tmpdir } from "../fixture/fixture"
diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts
index 37b938574..5e40cc047 100644
--- a/packages/sdk/js/src/v2/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts
@@ -4385,10 +4385,20 @@ export class Model extends HeyApiClient {
    *
    * Retrieve available v2 models ordered by release date.
    */
-  public list(options?: Options) {
+  public list(
+    parameters?: {
+      instance?: {
+        directory?: string
+        workspace?: string
+      }
+    },
+    options?: Options,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }])
     return (options?.client ?? this.client).get({
       url: "/api/model",
       ...options,
+      ...params,
     })
   }
 }
@@ -4399,10 +4409,20 @@ export class Provider2 extends HeyApiClient {
    *
    * Retrieve active v2 AI providers so clients can show provider availability and configuration.
    */
-  public list(options?: Options) {
+  public list(
+    parameters?: {
+      instance?: {
+        directory?: string
+        workspace?: string
+      }
+    },
+    options?: Options,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "instance" }] }])
     return (options?.client ?? this.client).get({
       url: "/api/provider",
       ...options,
+      ...params,
     })
   }
 
@@ -4414,10 +4434,24 @@ export class Provider2 extends HeyApiClient {
   public get(
     parameters: {
       providerID: string
+      instance?: {
+        directory?: string
+        workspace?: string
+      }
     },
     options?: Options,
   ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "providerID" }] }])
+    const params = buildClientParams(
+      [parameters],
+      [
+        {
+          args: [
+            { in: "path", key: "providerID" },
+            { in: "query", key: "instance" },
+          ],
+        },
+      ],
+    )
     return (options?.client ?? this.client).get({
       url: "/api/provider/{providerID}",
       ...options,
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index 014a5fbab..1f755ec92 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -15,8 +15,6 @@ export type Event =
   | EventPermissionReplied
   | EventSessionDiff
   | EventSessionError
-  | EventInstallationUpdated
-  | EventInstallationUpdateAvailable
   | EventQuestionAsked
   | EventQuestionReplied
   | EventQuestionRejected
@@ -42,6 +40,8 @@ export type Event =
   | EventPtyUpdated
   | EventPtyExited
   | EventPtyDeleted
+  | EventInstallationUpdated
+  | EventInstallationUpdateAvailable
   | EventMessageUpdated
   | EventMessageRemoved
   | EventMessagePartUpdated
@@ -799,8 +799,6 @@ export type GlobalEvent = {
     | EventPermissionReplied
     | EventSessionDiff
     | EventSessionError
-    | EventInstallationUpdated
-    | EventInstallationUpdateAvailable
     | EventQuestionAsked
     | EventQuestionReplied
     | EventQuestionRejected
@@ -826,6 +824,8 @@ export type GlobalEvent = {
     | EventPtyUpdated
     | EventPtyExited
     | EventPtyDeleted
+    | EventInstallationUpdated
+    | EventInstallationUpdateAvailable
     | EventMessageUpdated
     | EventMessageRemoved
     | EventMessagePartUpdated
@@ -2496,22 +2496,6 @@ export type EventSessionError = {
   }
 }
 
-export type EventInstallationUpdated = {
-  id: string
-  type: "installation.updated"
-  properties: {
-    version: string
-  }
-}
-
-export type EventInstallationUpdateAvailable = {
-  id: string
-  type: "installation.update-available"
-  properties: {
-    version: string
-  }
-}
-
 export type EventQuestionAsked = {
   id: string
   type: "question.asked"
@@ -2681,6 +2665,22 @@ export type EventPtyDeleted = {
   }
 }
 
+export type EventInstallationUpdated = {
+  id: string
+  type: "installation.updated"
+  properties: {
+    version: string
+  }
+}
+
+export type EventInstallationUpdateAvailable = {
+  id: string
+  type: "installation.update-available"
+  properties: {
+    version: string
+  }
+}
+
 export type EventMessageUpdated = {
   id: string
   type: "message.updated"
@@ -6673,7 +6673,12 @@ export type V2SessionMessagesResponse2 = V2SessionMessagesResponses[keyof V2Sess
 export type V2ModelListData = {
   body?: never
   path?: never
-  query?: never
+  query?: {
+    instance?: {
+      directory?: string
+      workspace?: string
+    }
+  }
   url: "/api/model"
 }
 
@@ -6689,7 +6694,12 @@ export type V2ModelListResponse = V2ModelListResponses[keyof V2ModelListResponse
 export type V2ProviderListData = {
   body?: never
   path?: never
-  query?: never
+  query?: {
+    instance?: {
+      directory?: string
+      workspace?: string
+    }
+  }
   url: "/api/provider"
 }
 
@@ -6707,7 +6717,12 @@ export type V2ProviderGetData = {
   path: {
     providerID: string
   }
-  query?: never
+  query?: {
+    instance?: {
+      directory?: string
+      workspace?: string
+    }
+  }
   url: "/api/provider/{providerID}"
 }
 

From faf871305313c3420f611a178166119b00aadc99 Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]" 
Date: Thu, 14 May 2026 00:59:33 +0000
Subject: [PATCH 312/378] chore: generate

---
 packages/core/src/models-snapshot.js          | 71725 +++++++++++++++-
 packages/core/src/plugin/boot.ts              |    67 +-
 .../instance/httpapi/groups/v2/model.ts       |    16 +-
 .../instance/httpapi/groups/v2/provider.ts    |    33 +-
 packages/sdk/openapi.json                     |   183 +-
 5 files changed, 71909 insertions(+), 115 deletions(-)

diff --git a/packages/core/src/models-snapshot.js b/packages/core/src/models-snapshot.js
index 817cf7509..c582a75bd 100644
--- a/packages/core/src/models-snapshot.js
+++ b/packages/core/src/models-snapshot.js
@@ -1,3 +1,71726 @@
 // @ts-nocheck
 // Auto-generated by build.ts - do not edit
-export const snapshot = {"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":{"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}},"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}},"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}},"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}},"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}},"grok-4.20-multi-agent-beta-0309":{"id":"grok-4.20-multi-agent-beta-0309","name":"grok-4.20-multi-agent-beta-0309","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":2000000,"output":30000}},"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}},"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-16","last_updated":"2025-10-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":5},"limit":{"context":200000,"output":64000}},"claude-opus-4-5-20251101":{"id":"claude-opus-4-5-20251101","name":"claude-opus-4-5-20251101","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-03-31","release_date":"2025-11-25","last_updated":"2025-11-25","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25},"limit":{"context":200000,"output":64000}},"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}},"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}},"glm-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video","audio","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.72,"output":3.2},"limit":{"context":200000,"output":131072}},"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.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.286,"output":1.142},"limit":{"context":204800,"output":131072}},"claude-3-5-haiku-20241022":{"id":"claude-3-5-haiku-20241022","name":"claude-3-5-haiku-20241022","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},"limit":{"context":200000,"output":8192}},"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}},"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}},"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-12","last_updated":"2026-02-12","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":2.6},"limit":{"context":204800,"output":131072}},"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}},"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}},"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-31","release_date":"2026-02-18","last_updated":"2026-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":1000000,"output":64000}},"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}},"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-20","last_updated":"2026-01-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.0715,"output":0.429},"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}},"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}},"grok-4.20-beta-0309-non-reasoning":{"id":"grok-4.20-beta-0309-non-reasoning","name":"grok-4.20-beta-0309-non-reasoning","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":2000000,"output":30000}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"claude-opus-4-7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2026-01-31","release_date":"2026-04-17","last_updated":"2026-04-17","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}},"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-08","last_updated":"2025-08-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":2},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"MiniMax-M2.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2},"limit":{"context":204800,"output":131072}},"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}},"claude-sonnet-4-5-20250929":{"id":"claude-sonnet-4-5-20250929","name":"claude-sonnet-4-5-20250929","family":"claude-sonnet","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-07-31","release_date":"2025-09-30","last_updated":"2025-09-30","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":200000,"output":64000}},"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}},"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}},"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}},"claude-3-5-haiku-latest":{"id":"claude-3-5-haiku-latest","name":"claude-3-5-haiku-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},"limit":{"context":200000,"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}},"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-12","last_updated":"2025-12-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.75,"output":14},"limit":{"context":400000,"input":272000,"output":128000}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"gpt-5.4-mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5},"limit":{"context":400000,"input":272000,"output":128000}},"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-5.1":{"id":"glm-5.1","name":"glm-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-10","last_updated":"2026-04-10","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.86,"output":3.5},"limit":{"context":200000,"output":131072}},"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}},"gpt-5.4-nano":{"id":"gpt-5.4-nano","name":"gpt-5.4-nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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-29","last_updated":"2025-07-29","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.286,"output":1.142},"limit":{"context":131072,"output":98304}},"gpt-5.4-mini-2026-03-17":{"id":"gpt-5.4-mini-2026-03-17","name":"gpt-5.4-mini-2026-03-17","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5},"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":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}},"gpt-5.2-chat-latest":{"id":"gpt-5.2-chat-latest","name":"gpt-5.2-chat-latest","family":"gpt-codex","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","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}},"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}},"gemini-3.1-flash-image-preview":{"id":"gemini-3.1-flash-image-preview","name":"gemini-3.1-flash-image-preview","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2025-01","release_date":"2026-02-27","last_updated":"2026-02-27","modalities":{"input":["text","image","pdf"],"output":["text","image"]},"open_weights":false,"cost":{"input":0.5,"output":60},"limit":{"context":131072,"output":32768}},"MiniMax-M2.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":4.8},"limit":{"context":204800,"output":131072}},"glm-4.5-x":{"id":"glm-4.5-x","name":"glm-4.5-x","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2025-07-29","last_updated":"2025-07-29","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.143,"output":2.29},"limit":{"context":128000,"output":16384}},"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}},"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"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":10},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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}},"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}},"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}},"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-29","last_updated":"2025-07-29","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1143,"output":0.286},"limit":{"context":131072,"output":98304}},"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,"cache_read":0,"cache_write":0,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"output":128000}},"glm-5-turbo":{"id":"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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.72,"output":3.2},"limit":{"context":200000,"output":131072}},"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}},"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-25","last_updated":"2025-11-25","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25},"limit":{"context":200000,"output":64000}},"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-12","last_updated":"2025-08-12","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.29,"output":0.86},"limit":{"context":64000,"output":16384}},"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.286,"output":1.142},"limit":{"context":204800,"output":131072}},"claude-opus-4-6-thinking":{"id":"claude-opus-4-6-thinking","name":"claude-opus-4-6-thinking","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-02-06","last_updated":"2026-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25},"limit":{"context":1000000,"output":128000}},"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}},"claude-sonnet-4-6-thinking":{"id":"claude-sonnet-4-6-thinking","name":"claude-sonnet-4-6-thinking","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08","release_date":"2026-02-18","last_updated":"2026-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":1000000,"output":64000}},"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.145,"output":0.43},"limit":{"context":128000,"output":32768}},"claude-opus-4-1-20250805":{"id":"claude-opus-4-1-20250805","name":"claude-opus-4-1-20250805","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},"limit":{"context":200000,"output":32000}},"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,"cache_write":0,"context_over_200k":{"input":5,"output":22.5}},"limit":{"context":1050000,"input":922000,"output":128000}},"gpt-5.1-chat-latest":{"id":"gpt-5.1-chat-latest","name":"gpt-5.1-chat-latest","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"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":10},"limit":{"context":128000,"output":16384}},"claude-haiku-4-5-20251001":{"id":"claude-haiku-4-5-20251001","name":"claude-haiku-4-5-20251001","family":"claude-haiku","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-02-28","release_date":"2025-10-16","last_updated":"2025-10-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":5},"limit":{"context":200000,"output":64000}},"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}},"gpt-5.4-nano-2026-03-17":{"id":"gpt-5.4-nano-2026-03-17","name":"gpt-5.4-nano-2026-03-17","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-19","last_updated":"2026-03-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25},"limit":{"context":400000,"input":272000,"output":128000}},"claude-sonnet-4-20250514":{"id":"claude-sonnet-4-20250514","name":"claude-sonnet-4-20250514","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},"limit":{"context":200000,"output":64000}},"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}},"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-06","last_updated":"2026-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25},"limit":{"context":1000000,"output":128000}},"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}},"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},"limit":{"context":1047576,"output":32768}},"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}},"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-08","last_updated":"2025-10-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":120},"limit":{"context":400000,"input":272000,"output":272000}},"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-05-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10},"limit":{"context":128000,"output":16384}},"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-30","last_updated":"2025-09-30","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"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-08","last_updated":"2025-08-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":10},"limit":{"context":400000,"input":272000,"output":128000}},"grok-4.20-beta-0309-reasoning":{"id":"grok-4.20-beta-0309-reasoning","name":"grok-4.20-beta-0309-reasoning","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":2000000,"output":30000}},"claude-opus-4-20250514":{"id":"claude-opus-4-20250514","name":"claude-opus-4-20250514","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},"limit":{"context":200000,"output":32000}},"glm-for-coding":{"id":"glm-for-coding","name":"glm-for-coding","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-09-30","last_updated":"2025-09-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.086,"output":0.343},"limit":{"context":200000,"output":131072}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":200000,"output":64000}},"glm-4.5-airx":{"id":"glm-4.5-airx","name":"glm-4.5-airx","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2025-07-29","last_updated":"2025-07-29","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.572,"output":1.714},"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,"structured_output":true,"temperature":true,"knowledge":"2024-04","release_date":"2025-04-14","last_updated":"2025-04-14","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8},"limit":{"context":1047576,"output":32768}},"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}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":1.6},"limit":{"context":1047576,"output":32768}},"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}},"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}},"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}}}},"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":{"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.5-122b-a10b":{"id":"qwen3.5-122b-a10b","name":"Qwen3.5 122B-A10B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-23","last_updated":"2026-02-23","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":3.2},"limit":{"context":262144,"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":1,"output":5},"limit":{"context":1048576,"output":65536}},"qwen3.6-27b":{"id":"qwen3.6-27b","name":"Qwen3.6 27B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":65536}},"qwen3.5-27b":{"id":"qwen3.5-27b","name":"Qwen3.5 27B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-23","last_updated":"2026-02-23","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.4},"limit":{"context":262144,"output":65536}},"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}},"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-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.5-397b-a17b":{"id":"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-15","last_updated":"2026-02-15","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":65536}},"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}},"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}},"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}},"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}},"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-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}},"qwen3.6-35b-a3b":{"id":"qwen3.6-35b-a3b","name":"Qwen3.6 35B-A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.248,"output":1.485},"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-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-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}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.276,"output":1.651,"cache_read":0.028,"cache_write":0.344},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"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-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}},"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}},"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}},"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}},"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-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-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-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-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}},"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}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.3,"output":7.8,"cache_read":0.13,"cache_write":1.625},"limit":{"context":262144,"output":65536}},"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}},"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}},"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}},"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}},"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.5-35b-a3b":{"id":"qwen3.5-35b-a3b","name":"Qwen3.5 35B-A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-23","last_updated":"2026-02-23","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2},"limit":{"context":262144,"output":65536}}}},"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":{"qwen3-embedding-8b":{"id":"qwen3-embedding-8b","name":"Qwen3 Embedding 8B","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-25-11","last_updated":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.1,"output":0},"limit":{"context":32768,"output":4096}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.75,"output":2.25},"limit":{"context":260000,"output":16384}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.9,"output":0.9},"limit":{"context":100000,"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-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":256000,"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-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2},"limit":{"context":256000,"output":16384}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.9,"output":0.9},"limit":{"context":32000,"output":8196}},"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":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":128000,"output":4096}},"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":"2026-03-17","modalities":{"input":["audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.003,"output":0},"limit":{"context":0,"output":8192}},"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":"2026-03-17","modalities":{"input":["text","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.35},"limit":{"context":32000,"output":16384}},"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":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":0.5},"limit":{"context":40000,"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.1,"output":0},"limit":{"context":8191,"output":3072}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.8},"limit":{"context":128000,"output":32768}},"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":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.35},"limit":{"context":128000,"output":32768}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":128000,"output":32768}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":128000,"output":8192}},"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":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":128000,"output":16384}}}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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.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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"qwen-3.6-plus":{"id":"qwen-3.6-plus","name":"Qwen 3.6 Plus","family":"qwen3.6","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.45,"output":2.7},"limit":{"context":991800,"output":65536}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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-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-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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen3.6","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"release_date":"2026-04-20","last_updated":"2026-04-21","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.3,"output":7.8},"limit":{"context":245800,"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"alibaba/qwen3.6-flash":{"id":"alibaba/qwen3.6-flash","name":"Qwen3.6 Flash","family":"qwen3.6","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.19,"output":1.16},"limit":{"context":991800,"output":65536}},"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}},"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}},"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}},"LLM360/K2-Think":{"id":"LLM360/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}},"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/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/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/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/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/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-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/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/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/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/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/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/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/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}},"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.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/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/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/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}},"CrucibleLab/L3.3-70B-Loki-V2.0":{"id":"CrucibleLab/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}},"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}},"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}},"Doctor-Shotgun/MS3.2-24B-Magnum-Diamond":{"id":"Doctor-Shotgun/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}},"NeverSleep/Llama-3-Lumimaid-70B-v0.1":{"id":"NeverSleep/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/Lumimaid-v0.2-70B":{"id":"NeverSleep/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}},"Steelskull/L3.3-Cu-Mai-R1-70b":{"id":"Steelskull/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/L3.3-Nevoria-R1-70b":{"id":"Steelskull/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/L3.3-MS-Evayale-70B":{"id":"Steelskull/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}},"Steelskull/L3.3-Electra-R1-70b":{"id":"Steelskull/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/L3.3-MS-Nevoria-70b":{"id":"Steelskull/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/L3.3-MS-Evalebis-70b":{"id":"Steelskull/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}},"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}},"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}},"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}},"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}},"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}},"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}},"Salesforce/Llama-xLAM-2-70b-fc-r":{"id":"Salesforce/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}},"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/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: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}},"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/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}},"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/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}},"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}},"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/Llama-3.05-NT-Storybreaker-Ministral-70B":{"id":"Envoid/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}},"Envoid/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B":{"id":"Envoid/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}},"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}},"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}},"ReadyArt/MS3.2-The-Omega-Directive-24B-Unslop-v2.0":{"id":"ReadyArt/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}},"ReadyArt/The-Omega-Abomination-L-70B-v1.0":{"id":"ReadyArt/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}},"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}},"MarinaraSpaghetti/NemoMix-Unleashed-12B":{"id":"MarinaraSpaghetti/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}},"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}},"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}},"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}},"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":{"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-5.1":{"id":"zai-org/glm-5.1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.55},"limit":{"context":200000,"input":200000,"output":131072}},"zai-org/glm-5.1:thinking":{"id":"zai-org/glm-5.1:thinking","name":"GLM 5.1 Thinking","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.55},"limit":{"context":200000,"input":200000,"output":131072}},"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}},"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}},"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}},"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}},"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}},"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-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-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/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/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/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/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/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/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}},"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-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/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-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-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-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/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/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}},"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}},"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-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-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.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.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-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}},"GalrionSoftworks/MN-LooseCannon-12B-v1":{"id":"GalrionSoftworks/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}},"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}},"Gryphe/MythoMax-L2-13b":{"id":"Gryphe/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}},"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-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}},"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-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":{"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.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}},"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}},"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}},"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}},"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}},"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":true,"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}},"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}},"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/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-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/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}},"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.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/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/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/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-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}},"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-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}},"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: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}},"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-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-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-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}},"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-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/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/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/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/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/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.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/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/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/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/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-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/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/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-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.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/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-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/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/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-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/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-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.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-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}},"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/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/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-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-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-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-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/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-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/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/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-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-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-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/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/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/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}},"VongolaChouko/Starcannon-Unleashed-12B-v1.0":{"id":"VongolaChouko/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}},"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-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}},"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-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}},"Sao10K/L3.3-70B-Euryale-v2.3":{"id":"Sao10K/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/L3.1-70B-Euryale-v2.2":{"id":"Sao10K/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/L3.1-70B-Hanami-x1":{"id":"Sao10K/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/L3-8B-Stheno-v3.2":{"id":"Sao10K/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}},"LatitudeGames/Wayfarer-Large-70B-Llama-3.3":{"id":"LatitudeGames/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}},"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":{"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}},"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: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}},"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}},"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}},"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}},"Infermatic/MN-12B-Inferor-v0.0":{"id":"Infermatic/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}},"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}},"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}},"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-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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2},"limit":{"context":204800,"input":204800,"output":131072}},"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}},"qwen/Qwen3.6-35B-A3B:thinking":{"id":"qwen/Qwen3.6-35B-A3B:thinking","name":"Qwen3.6 35B A3B Thinking","family":"qwen3.6","attachment":false,"reasoning":true,"tool_call":false,"structured_output":false,"release_date":"2026-04-19","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.29,"output":1.74},"limit":{"context":262144,"output":16384}},"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}},"qwen/Qwen3.6-35B-A3B":{"id":"qwen/Qwen3.6-35B-A3B","name":"Qwen3.6 35B A3B","family":"qwen3.6","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"release_date":"2026-04-17","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.29,"output":1.74},"limit":{"context":262144,"output":16384}},"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}},"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-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-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}},"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-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}},"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-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-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}},"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-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-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}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":false,"tool_call":true,"structured_output":false,"release_date":"2026-04-16","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.53,"output":2.73},"limit":{"context":256000,"output":65536}},"moonshotai/kimi-k2.6:thinking":{"id":"moonshotai/kimi-k2.6:thinking","name":"Kimi K2.6 Thinking","family":"kimi-thinking","attachment":true,"reasoning":true,"tool_call":true,"structured_output":false,"release_date":"2026-04-16","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.53,"output":2.73},"limit":{"context":256000,"output":65536}},"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-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.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}},"Tongyi-Zhiwen/QwenLong-L1-32B":{"id":"Tongyi-Zhiwen/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}},"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}},"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}},"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-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}},"Alibaba-NLP/Tongyi-DeepResearch-30B-A3B":{"id":"Alibaba-NLP/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}},"MiniMaxAI/MiniMax-M1-80k":{"id":"MiniMaxAI/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}},"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":{"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}},"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,"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":2.992,"output":14.993999999999998},"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: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-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,"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":2.992,"output":14.993999999999998},"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}},"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}},"EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.0":{"id":"EVA-UNIT-01/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/EVA-Qwen2.5-72B-v0.2":{"id":"EVA-UNIT-01/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/EVA-LLaMA-3.33-70B-v0.1":{"id":"EVA-UNIT-01/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/EVA-Qwen2.5-32B-v0.2":{"id":"EVA-UNIT-01/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}},"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/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/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/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/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}},"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}},"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":{"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-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}},"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}},"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}},"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}}}},"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-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}},"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}},"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}},"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}},"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-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},"limit":{"context":200000,"output":64000}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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.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}},"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}},"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}},"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}},"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-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}},"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.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}},"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-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}},"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}},"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}},"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}},"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}},"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-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-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}},"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}},"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-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}},"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}},"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}},"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},"limit":{"context":200000,"output":128000}},"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-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}},"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}},"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}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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/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/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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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":{"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}},"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}},"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}},"openai/gpt-5.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"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}},"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.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.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}},"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-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-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}},"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}},"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-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-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},"limit":{"context":200000,"output":64000}},"anthropic/claude-opus-4-7":{"id":"anthropic/claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":25,"cache_read":0.5},"limit":{"context":1000000,"output":128000}},"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-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-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},"limit":{"context":200000,"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},"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":{"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}},"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-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-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-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/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.6-35B-A3B":{"id":"Qwen/Qwen3.6-35B-A3B","name":"Qwen/Qwen3.6-35B-A3B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.23,"output":1.86},"limit":{"context":262144,"output":65536}},"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/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-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-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-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/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/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-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/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-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-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/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-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-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-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-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-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-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-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-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-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-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-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/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-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/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-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-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-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-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/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}},"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}},"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.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-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}},"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/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}},"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}},"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}},"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.1":{"id":"Pro/zai-org/GLM-5.1","name":"Pro/zai-org/GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_write":0},"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/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-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.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-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}},"Pro/moonshotai/Kimi-K2.6":{"id":"Pro/moonshotai/Kimi-K2.6","name":"Pro/moonshotai/Kimi-K2.6","family":"kimi","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262000,"output":262000}},"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,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"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.25},"limit":{"context":262000,"output":262000}},"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}},"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}},"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}},"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.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-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":{"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-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-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-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}},"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}},"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-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-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}},"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-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}},"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}}}},"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":{"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}},"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-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}},"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-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}},"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}},"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-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":{"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":{"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.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"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}}}},"perplexity":{"id":"perplexity","env":["PERPLEXITY_API_KEY"],"npm":"@ai-sdk/perplexity","name":"Perplexity","doc":"https://docs.perplexity.ai","models":{"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}},"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":{"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-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}}}},"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-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.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"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.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}}}},"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":{"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-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-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}},"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}},"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-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-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}}}},"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":{"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}},"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}},"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-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-r1":{"id":"deepseek/deepseek-r1","name":"DeepSeek: R1","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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":0.7,"output":2.5},"limit":{"context":64000,"output":16000}},"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-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.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-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-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}},"openrouter/owl-alpha":{"id":"openrouter/owl-alpha","name":"Owl Alpha","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":1048756,"output":262144},"status":"alpha"},"openrouter/pareto-code":{"id":"openrouter/pareto-code","name":"Pareto Code Router","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":200000,"output":200000}},"openrouter/elephant-alpha":{"id":"openrouter/elephant-alpha","name":"Elephant (free)","family":"elephant","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-13","last_updated":"2026-04-13","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"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}},"arcee-ai/trinity-large-thinking":{"id":"arcee-ai/trinity-large-thinking","name":"Trinity Large Thinking","family":"trinity","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.22,"output":0.85},"limit":{"context":262144,"output":80000}},"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}},"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}},"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}},"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-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-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-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-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}},"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}},"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}},"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-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/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/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}},"mistralai/mistral-small-2603":{"id":"mistralai/mistral-small-2603","name":"Mistral Small 4","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":262144,"output":262144}},"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/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-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/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-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}},"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.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}},"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-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-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-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}},"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-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.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-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-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":{"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}},"tencent/hy3-preview":{"id":"tencent/hy3-preview","name":"Hy3 preview","family":"Hy","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.066,"output":0.26,"cache_read":0.029,"cache_write":0.029},"limit":{"context":256000,"output":64000}},"poolside/laguna-m.1:free":{"id":"poolside/laguna-m.1:free","name":"Laguna M.1","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":8192}},"poolside/laguna-xs.2:free":{"id":"poolside/laguna-xs.2:free","name":"Laguna XS.2","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":8192}},"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}},"nvidia/nemotron-3-super-120b-a12b":{"id":"nvidia/nemotron-3-super-120b-a12b","name":"Nemotron 3 Super","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.5},"limit":{"context":262144,"output":262144}},"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free":{"id":"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free","name":"Nemotron 3 Nano Omni (free)","family":"nemotron","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-28","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":256000,"output":65536}},"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: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-3-super-120b-a12b:free":{"id":"nvidia/nemotron-3-super-120b-a12b:free","name":"Nemotron 3 Super (free)","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"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}},"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}},"inception/mercury-edit-2":{"id":"inception/mercury-edit-2","name":"Mercury Edit 2","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":0.75,"cache_read":0.025},"limit":{"context":128000,"output":8192}},"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}},"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-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-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-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.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-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}},"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.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-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.4-mini":{"id":"openai/gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"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/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-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"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-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-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":{"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.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-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-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-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-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":{"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/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-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-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-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.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}},"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.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-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-5.1":{"id":"z-ai/glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202752,"output":131072}},"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.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-5-turbo":{"id":"z-ai/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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.96,"output":3.2,"cache_read":0.192,"cache_write":0},"limit":{"context":202752,"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}},"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-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}},"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}},"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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"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-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.5:free":{"id":"minimax/minimax-m2.5:free","name":"MiniMax M2.5 (free)","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,"output":0},"limit":{"context":204800,"output":131072}},"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-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}},"qwen/qwen3-coder-plus":{"id":"qwen/qwen3-coder-plus","name":"Qwen3 Coder Plus","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2025-09-23","last_updated":"2025-09-23","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.65,"output":3.25,"cache_read":0.13,"cache_write":0.8125},"limit":{"context":1000000,"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,"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/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/qwen-plus":{"id":"qwen/qwen-plus","name":"Qwen: Qwen-Plus","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"structured_output":false,"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.26,"output":0.78,"cache_read":0.052,"cache_write":0.325},"limit":{"context":1000000,"output":32768}},"qwen/qwen3.5-flash-02-23":{"id":"qwen/qwen3.5-flash-02-23","name":"Qwen: Qwen3.5-Flash","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-25","last_updated":"2026-02-25","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.065,"output":0.26},"limit":{"context":1000000,"output":65536}},"qwen/qwen3.6-plus":{"id":"qwen/qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.325,"output":1.95,"cache_read":0.0325,"cache_write":0.40625},"limit":{"context":1000000,"output":65536}},"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,"cache_read":0.156,"cache_write":0.975},"limit":{"context":262144,"output":32768}},"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/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/qwen-3.6-27b":{"id":"qwen/qwen-3.6-27b","name":"Qwen3.6 27B","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.195,"output":1.56},"limit":{"context":262144,"output":81920}},"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-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/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/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,"cache_read":0.039,"cache_write":0.24375},"limit":{"context":128000,"output":66536}},"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/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-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-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/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-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}},"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/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/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/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-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/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.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/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-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-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/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-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/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-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Gemma 4 31B","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.4},"limit":{"context":262144,"output":262144}},"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-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-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}},"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-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-flash","attachment":true,"reasoning":true,"tool_call":false,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"cost":{"input":0.5,"output":3},"limit":{"context":65536,"output":65536}},"google/gemma-4-31b-it:free":{"id":"google/gemma-4-31b-it:free","name":"Gemma 4 31B (free)","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"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/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/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/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/gemma-4-26b-a4b-it":{"id":"google/gemma-4-26b-a4b-it","name":"Gemma 4 26B A4B","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-03","last_updated":"2026-04-03","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.13,"output":0.4},"limit":{"context":262144,"output":262144}},"google/gemma-4-26b-a4b-it:free":{"id":"google/gemma-4-26b-a4b-it:free","name":"Gemma 4 26B A4B (free)","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-03","last_updated":"2026-04-03","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"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}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"structured_output":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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":{"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-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}},"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-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.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-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-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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":{"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.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-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-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,"knowledge":"2025-08-31","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}},"deepseek/deepseek-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1048576,"output":393216}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1048576,"output":393216}},"x-ai/grok-4.3":{"id":"x-ai/grok-4.3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2,"context_over_200k":{"input":2.5,"output":5,"cache_read":0.4}},"limit":{"context":1000000,"output":1000000}},"openai/gpt-5.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000}},"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.5-pro":{"id":"openai/gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"output":128000}},"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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"limit":{"context":1048576,"output":65536}},"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}},"xiaomi/mimo-v2.5-pro":{"id":"xiaomi/mimo-v2.5-pro","name":"Xiaomi: MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-omni":{"id":"xiaomi/mimo-v2-omni","name":"Xiaomi: MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":131072}},"xiaomi/mimo-v2.5":{"id":"xiaomi/mimo-v2.5","name":"Xiaomi: MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-pro":{"id":"xiaomi/mimo-v2-pro","name":"Xiaomi: MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-flash":{"id":"xiaomi/mimo-v2-flash","name":"Xiaomi: MiMo-V2-Flash","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"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":262144,"output":65536}}}},"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/glm-5p1":{"id":"accounts/fireworks/models/glm-5p1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202800,"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/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/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/kimi-k2p6":{"id":"accounts/fireworks/models/kimi-k2p6","name":"Kimi K2.6","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262000,"output":262000}},"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/qwen3p6-plus":{"id":"accounts/fireworks/models/qwen3p6-plus","name":"Qwen 3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-04","last_updated":"2026-04-04","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.1},"limit":{"context":128000,"output":8192}},"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/minimax-m2p7":{"id":"accounts/fireworks/models/minimax-m2p7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-12","last_updated":"2026-04-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/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-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/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/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}},"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-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/routers/kimi-k2p5-turbo":{"id":"accounts/fireworks/routers/kimi-k2p5-turbo","name":"Kimi K2.5 Turbo","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"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":256000,"output":256000}},"accounts/fireworks/models/deepseek-v4-pro":{"id":"accounts/fireworks/models/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.15},"limit":{"context":1000000,"output":384000}}}},"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":{"k2p6":{"id":"k2p6","name":"Kimi K2.6","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04","last_updated":"2026-04","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}},"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}}}},"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}}}},"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":{"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax M2.7","family":"minimax-m2.7","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":204800,"output":131072},"provider":{"npm":"@ai-sdk/anthropic"}},"kimi-k2.5":{"id":"kimi-k2.5","name":"Kimi K2.5","family":"kimi-k2.5","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}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo V2.5 Pro","family":"mimo-v2.5-pro","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"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,"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":202752,"output":32768}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo V2 Omni","family":"mimo-v2-omni","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":128000},"status":"deprecated"},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo V2.5","family":"mimo-v2.5","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1000000,"output":128000}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05,"cache_write":0.625},"limit":{"context":262144,"output":65536},"provider":{"npm":"@ai-sdk/anthropic"}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-04","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202752,"output":32768}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.0028},"limit":{"context":1000000,"output":384000}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-10","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":65536}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.0145},"limit":{"context":1000000,"output":384000}},"minimax-m2.5":{"id":"minimax-m2.5","name":"MiniMax M2.5","family":"minimax-m2.5","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.03},"limit":{"context":204800,"output":65536}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo V2 Pro","family":"mimo-v2-pro","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":128000},"status":"deprecated"},"qwen3.5-plus":{"id":"qwen3.5-plus","name":"Qwen3.5 Plus","family":"qwen3.5","attachment":true,"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.2,"output":1.2,"cache_read":0.02,"cache_write":0.25},"limit":{"context":262144,"output":65536},"provider":{"npm":"@ai-sdk/anthropic"}}}},"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":{"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}},"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}},"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}},"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/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/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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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":{"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}},"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}},"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}},"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-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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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-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}},"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.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}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.276,"output":1.651,"cache_read":0.028,"cache_write":0.344},"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}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-14","last_updated":"2026-04-14","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.87,"output":3.48,"cache_read":0.17},"limit":{"context":202752,"output":128000}},"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}},"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}},"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-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-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}},"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-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-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-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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Moonshot Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":false,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.929,"output":3.858},"limit":{"context":262144,"output":16384}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-21","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.32,"output":7.9,"cache_read":0.132},"limit":{"context":245800,"output":65536}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"MiniMax/MiniMax-M2.7":{"id":"MiniMax/MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131072}},"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.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}},"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.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}},"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}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}}}},"firepass":{"id":"firepass","env":["FIREPASS_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.fireworks.ai/inference/v1/","name":"Fireworks (Firepass)","doc":"https://docs.fireworks.ai/firepass","models":{"accounts/fireworks/routers/kimi-k2p6-turbo":{"id":"accounts/fireworks/routers/kimi-k2p6-turbo","name":"Kimi K2.6 Turbo","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262000,"output":262000}}}},"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":{"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":{"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.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"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}}}},"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.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-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}},"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}},"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.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-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}},"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}},"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}},"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}},"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}},"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-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}},"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.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}},"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-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}},"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.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}},"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}},"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}},"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}},"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}},"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}},"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-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-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}},"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}},"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}},"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-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,"knowledge":"2025-05-31","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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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.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.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}},"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}},"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}},"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}},"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}},"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}},"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-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-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-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-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-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-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}},"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}},"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}}}},"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}}}},"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":{"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}},"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-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-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-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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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.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}},"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}}}},"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":{"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-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-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-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-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-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}},"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-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}},"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.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-03","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3.2,"cache_read":0.2},"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":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}},"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-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}},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi-K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-01","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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}},"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.7":{"id":"MiniMaxAI/MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"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-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1048576,"output":393216}}}},"zenmux":{"id":"zenmux","env":["ZENMUX_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://zenmux.ai/api/v1","name":"ZenMux","doc":"https://docs.zenmux.ai","models":{"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}},"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}},"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}},"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}},"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}},"kuaishou/kat-coder-pro-v2":{"id":"kuaishou/kat-coder-pro-v2","name":"KAT-Coder-Pro-V2","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":256000,"output":80000}},"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-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.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":{"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":{"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}},"x-ai/grok-4.2-fast":{"id":"x-ai/grok-4.2-fast","name":"Grok 4.2 Fast","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":9},"limit":{"context":2000000,"output":30000}},"x-ai/grok-4.2-fast-non-reasoning":{"id":"x-ai/grok-4.2-fast-non-reasoning","name":"Grok 4.2 Fast Non Reasoning","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":9},"limit":{"context":2000000,"output":30000}},"openai/gpt-5.3-chat":{"id":"openai/gpt-5.3-chat","name":"GPT-5.3 Chat","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.75,"output":14},"limit":{"context":128000,"output":16380},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.3-codex":{"id":"openai/gpt-5.3-codex","name":"GPT-5.3 Codex","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.75,"output":14},"limit":{"context":400000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.4-mini":{"id":"openai/gpt-5.4-mini","name":"GPT-5.4 Mini","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5},"limit":{"context":400000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"GPT-5.4 Nano","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25},"limit":{"context":400000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.4-pro":{"id":"openai/gpt-5.4-pro","name":"GPT-5.4 Pro","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":45,"output":225},"limit":{"context":1050000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"openai/gpt-5.4":{"id":"openai/gpt-5.4","name":"GPT-5.4","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3.75,"output":18.75},"limit":{"context":1050000,"output":128000},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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},"provider":{"npm":"@ai-sdk/openai","api":"https://zenmux.ai/api/v1"}},"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,"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,"output":0},"limit":{"context":200000,"output":64000}},"z-ai/glm-5v-turbo":{"id":"z-ai/glm-5v-turbo","name":"GLM 5V Turbo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.726,"output":3.1946,"cache_read":0.1743},"limit":{"context":200000,"output":128000}},"z-ai/glm-4.7":{"id":"z-ai/glm-4.7","name":"GLM 4.7","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"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-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.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-5.1":{"id":"z-ai/glm-5.1","name":"GLM-5.1","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-03","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8781,"output":3.5126,"cache_read":0.1903},"limit":{"context":200000,"output":131072}},"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.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.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-5-turbo":{"id":"z-ai/glm-5-turbo","name":"GLM 5 Turbo","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.88,"output":3.48},"limit":{"context":200000,"output":128000}},"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.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}},"volcengine/doubao-seed-2.0-code":{"id":"volcengine/doubao-seed-2.0-code","name":"Doubao Seed 2.0 Code","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.9,"output":4.48},"limit":{"context":256000,"output":32000}},"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-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-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}},"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}},"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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax M2.7","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3055,"output":1.2219},"limit":{"context":204800,"output":131070},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"minimax/minimax-m2.7-highspeed":{"id":"minimax/minimax-m2.7-highspeed","name":"MiniMax M2.7 highspeed","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.611,"output":2.4439},"limit":{"context":204800,"output":131070},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"minimax/minimax-m2.5-lightning":{"id":"minimax/minimax-m2.5-lightning","name":"MiniMax M2.5 highspeed","attachment":false,"reasoning":true,"tool_call":true,"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"minimax/minimax-m2.5":{"id":"minimax/minimax-m2.5","name":"MiniMax M2.5","attachment":false,"reasoning":true,"tool_call":true,"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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}},"qwen/qwen3.5-flash":{"id":"qwen/qwen3.5-flash","name":"Qwen3.5 Flash","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.1,"output":0.4},"limit":{"context":1020000,"output":1020000}},"qwen/qwen3.6-plus":{"id":"qwen/qwen3.6-plus","name":"Qwen3.6-Plus","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05,"cache_write":0.625,"context_over_200k":{"input":2,"output":6,"cache_read":0.2,"cache_write":2.5}},"limit":{"context":1000000,"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.5-plus":{"id":"qwen/qwen3.5-plus","name":"Qwen3.5 Plus","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01-01","release_date":"2026-03-20","last_updated":"2026-03-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":4.8},"limit":{"context":1000000,"output":64000}},"google/gemini-3.1-flash-lite-preview":{"id":"google/gemini-3.1-flash-lite-preview","name":"Gemini 3.1 Flash Lite Preview","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-03-20","last_updated":"2025-03-20","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1.5},"limit":{"context":1050000,"output":65530}},"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-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-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}},"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-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}},"sapiens-ai/agnes-1.5-lite":{"id":"sapiens-ai/agnes-1.5-lite","name":"Agnes 1.5 Lite","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-26","last_updated":"2026-03-26","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.12,"output":0.6},"limit":{"context":256000,"output":256000}},"sapiens-ai/agnes-1.5-pro":{"id":"sapiens-ai/agnes-1.5-pro","name":"Agnes 1.5 Pro","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-21","last_updated":"2026-03-21","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.16,"output":0.8},"limit":{"context":256000,"output":256000}},"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":false,"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-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}},"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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":false,"knowledge":"2025-01-01","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262140,"output":262140}},"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}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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-05-31","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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"anthropic/claude-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Claude Opus 4.7","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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":32000},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"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-08-31","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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://zenmux.ai/api/anthropic/v1"}},"deepseek/deepseek-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}},"tencent/hy3-preview":{"id":"tencent/hy3-preview","name":"Hy3 preview","family":"Hy","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.172,"output":0.572,"cache_read":0.058,"cache_write":0},"limit":{"context":256000,"output":64000}},"openai/gpt-5.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"experimental":{"modes":{"fast":{"cost":{"input":12.5,"output":75,"cache_read":1.25},"provider":{"body":{"service_tier":"priority"}}}}}},"openai/gpt-5.5-pro":{"id":"openai/gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"output":128000}},"xiaomi/mimo-v2.5-pro":{"id":"xiaomi/mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-omni":{"id":"xiaomi/mimo-v2-omni","name":"MiMo V2 Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":265000,"output":265000}},"xiaomi/mimo-v2.5":{"id":"xiaomi/mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-pro":{"id":"xiaomi/mimo-v2-pro","name":"MiMo V2 Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1000000,"output":256000}},"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-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":262144,"output":65536}}}},"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}}}},"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":{"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-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-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-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-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-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-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-qwen-32b":{"id":"deepseek/deepseek-r1-distill-qwen-32b","name":"DeepSeek R1 Distill Qwen 32B","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-01-20","last_updated":"2025-01-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.3},"limit":{"context":64000,"output":32000}},"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}},"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-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-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-r1-distill-qwen-14b":{"id":"deepseek/deepseek-r1-distill-qwen-14b","name":"DeepSeek R1 Distill Qwen 14B","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-01-20","last_updated":"2025-01-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.15},"limit":{"context":32768,"output":16384}},"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-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}},"inclusionai/ling-2.6-1t":{"id":"inclusionai/ling-2.6-1t","name":"Ling-2.6-1T","family":"ling","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"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}},"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}},"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/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-5.1":{"id":"zai-org/glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"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,"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.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.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.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.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}},"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/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}},"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}},"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}},"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.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-3.2-3b-instruct":{"id":"meta-llama/llama-3.2-3b-instruct","name":"Llama 3.2 3B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-09-18","last_updated":"2024-09-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.03,"output":0.05},"limit":{"context":32768,"output":32000}},"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-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-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-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}},"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}},"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-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/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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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-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-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}},"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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax M2.7","family":"minimax-m2.7","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"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.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.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}},"minimax/minimax-m2.5-highspeed":{"id":"minimax/minimax-m2.5-highspeed","name":"MiniMax M2.5 Highspeed","family":"minimax-m2.5","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.6,"output":2.4,"cache_read":0.03},"limit":{"context":204800,"output":131100}},"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.5-122b-a10b":{"id":"qwen/qwen3.5-122b-a10b","name":"Qwen3.5-122B-A10B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":3.2},"limit":{"context":262144,"output":65536}},"qwen/qwen3.6-27b":{"id":"qwen/qwen3.6-27b","name":"Qwen3.6-27B","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":65536}},"qwen/qwen3.5-27b":{"id":"qwen/qwen3.5-27b","name":"Qwen3.5-27B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.4},"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,"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-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/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-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-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-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}},"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-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-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-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/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/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-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-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-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-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-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-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-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-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-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.5-35b-a3b":{"id":"qwen/qwen3.5-35b-a3b","name":"Qwen3.5-35B-A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2},"limit":{"context":262144,"output":65536}},"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}},"google/gemma-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Gemma 4 31B","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.4},"limit":{"context":262144,"output":131072}},"google/gemma-3-12b-it":{"id":"google/gemma-3-12b-it","name":"Gemma 3 12B","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.05,"output":0.1},"limit":{"context":131072,"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,"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}},"google/gemma-4-26b-a4b-it":{"id":"google/gemma-4-26b-a4b-it","name":"Gemma 4 26B A4B","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.13,"output":0.4},"limit":{"context":262144,"output":131072}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"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}},"deepseek/deepseek-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1048576,"output":393216}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.69,"output":3.38,"cache_read":0.13},"limit":{"context":1048576,"output":393216}}}},"xiaomi-token-plan-cn":{"id":"xiaomi-token-plan-cn","env":["XIAOMI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://token-plan-cn.xiaomimimo.com/v1","name":"Xiaomi Token Plan (China)","doc":"https://platform.xiaomimimo.com/#/docs","models":{"mimo-v2-tts":{"id":"mimo-v2-tts","name":"MiMo-V2-TTS","family":"mimo","attachment":false,"reasoning":false,"tool_call":false,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["audio"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":8192,"output":16384}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262144,"output":131072}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"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,"output":0,"cache_read":0},"limit":{"context":262144,"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":{"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-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}},"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}},"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}},"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.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}},"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.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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"zai-org/GLM-5.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.4,"output":4.4,"cache_read":0.26,"cache_write":0},"limit":{"context":200000,"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":{"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}},"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-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.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/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-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}},"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-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-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-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-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-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-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/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-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/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}},"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.39,"output":2.34,"cache_read":0.195},"limit":{"context":262144,"output":65536}},"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/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/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}},"zai-org/GLM-5.1-TEE":{"id":"zai-org/GLM-5.1-TEE","name":"GLM 5.1 TEE","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":3.15,"cache_read":0.475},"limit":{"context":202752,"output":65535}},"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.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-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-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-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-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.95,"output":3.15,"cache_read":0.475},"limit":{"context":202752,"output":65535}},"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-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.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,"cache_read":0.15},"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}},"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.4,"output":1.7,"cache_read":0.2},"limit":{"context":202752,"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}},"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}},"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}},"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}},"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-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-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-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}},"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.28,"output":0.42,"cache_read":0.14},"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}},"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}},"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/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}},"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/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/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-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}},"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}},"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.6-TEE":{"id":"moonshotai/Kimi-K2.6-TEE","name":"Kimi K2.6 TEE","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-12","release_date":"2026-04-20","last_updated":"2026-04-23","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.44,"output":2},"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}},"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}},"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.3,"output":1.1,"cache_read":0.15},"limit":{"context":196608,"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}},"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/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/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/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}}}},"dinference":{"id":"dinference","env":["DINFERENCE_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.dinference.com/v1","name":"DInference","doc":"https://dinference.com","models":{"gpt-oss-120b":{"id":"gpt-oss-120b","name":"GPT OSS 120B","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-08","last_updated":"2025-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.0675,"output":0.27},"limit":{"context":131072,"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.45,"output":1.65},"limit":{"context":200000,"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":true,"cost":{"input":0.75,"output":2.4},"limit":{"context":200000,"output":128000}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":3.89},"limit":{"context":200000,"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.22,"output":0.88},"limit":{"context":200000,"output":32000}}}},"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":{"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}},"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},"provider":{"npm":"@ai-sdk/openai-compatible"}},"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},"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"}},"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.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000},"provider":{"npm":"@ai-sdk/openai-compatible"}},"gpt-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000},"provider":{"npm":"@ai-sdk/openai-compatible"}},"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}},"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},"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}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"provider":{"npm":"@ai-sdk/openai-compatible"}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000},"provider":{"npm":"@ai-sdk/openai-compatible"}}}},"deepinfra":{"id":"deepinfra","env":["DEEPINFRA_API_KEY"],"npm":"@ai-sdk/deepinfra","name":"Deep Infra","doc":"https://deepinfra.com/models","models":{"Qwen/Qwen3.5-397B-A17B":{"id":"Qwen/Qwen3.5-397B-A17B","name":"Qwen 3.5 397B A17B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-02-01","last_updated":"2026-04-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.54,"output":3.4},"limit":{"context":262144,"output":81920}},"Qwen/Qwen3.5-35B-A3B":{"id":"Qwen/Qwen3.5-35B-A3B","name":"Qwen 3.5 35B A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-02-01","last_updated":"2026-04-20","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.95},"limit":{"context":262144,"output":81920}},"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}},"Qwen/Qwen3.6-35B-A3B":{"id":"Qwen/Qwen3.6-35B-A3B","name":"Qwen3.6 35B A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":1},"limit":{"context":262144,"output":81920}},"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.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-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-5.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202752,"output":16384}},"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}},"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.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}},"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-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-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-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.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}},"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.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}},"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}},"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}},"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}},"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}},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.75,"output":3.5,"cache_read":0.15},"limit":{"context":262144,"output":16384}},"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}},"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.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.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}},"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}},"deepseek-ai/DeepSeek-V4-Flash":{"id":"deepseek-ai/DeepSeek-V4-Flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek-ai/DeepSeek-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":65536,"output":65536}},"google/gemma-4-26B-A4B-it":{"id":"google/gemma-4-26B-A4B-it","name":"Gemma 4 26B","family":"gemma","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.07,"output":0.34},"limit":{"context":256000,"output":8192}},"google/gemma-4-31B-it":{"id":"google/gemma-4-31B-it","name":"Gemma 4 31B","family":"gemma","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.13,"output":0.38},"limit":{"context":256000,"output":8192}}}},"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":{"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-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-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}},"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}},"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-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}},"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.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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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.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.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}},"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}},"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}},"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-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-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-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}},"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}},"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}},"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}},"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/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-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}},"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.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.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}},"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}},"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-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-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}},"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}},"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}},"mimo-v2-flash":{"id":"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-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":256000}},"xiaomi/mimo-v2-flash":{"id":"xiaomi/mimo-v2-flash","name":"Xiaomi/Mimo-V2-Flash","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"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":256000}}}},"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":{"rekaai/reka-edge":{"id":"rekaai/reka-edge","name":"Reka Edge","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-20","last_updated":"2026-04-11","modalities":{"input":["image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":16384,"output":16384}},"rekaai/reka-flash-3":{"id":"rekaai/reka-flash-3","name":"Reka Flash 3","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2025-03-12","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.2},"limit":{"context":65536,"output":65536}},"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}},"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}},"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}},"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}},"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}},"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}},"ibm-granite/granite-4.1-8b":{"id":"ibm-granite/granite-4.1-8b","name":"IBM: Granite 4.1 8B","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-30","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.05,"output":0.1,"cache_read":0.05},"limit":{"context":131072,"output":131072}},"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}},"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}},"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-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":{"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-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}},"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}},"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":{"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-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-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-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}},"deepseek/deepseek-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.14,"output":0.28,"cache_read":0.0028},"limit":{"context":1048576,"output":384000}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek: DeepSeek V4 Pro","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.435,"output":0.87,"cache_read":0.003625},"limit":{"context":1048576,"output":384000}},"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-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-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-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}},"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/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"},"openrouter/owl-alpha":{"id":"openrouter/owl-alpha","name":"Owl Alpha","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":1048756,"output":262144},"status":"alpha"},"openrouter/pareto-code":{"id":"openrouter/pareto-code","name":"Pareto Code Router","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-04-21","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":200000,"output":65536}},"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}},"inclusionai/ling-2.6-1t:free":{"id":"inclusionai/ling-2.6-1t:free","name":"inclusionAI: Ling-2.6-1T (free)","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":32768}},"inclusionai/ling-2.6-flash":{"id":"inclusionai/ling-2.6-flash","name":"inclusionAI: Ling-2.6 Flash","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.08,"output":0.24,"cache_read":0.016},"limit":{"context":262144,"output":32768}},"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/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-large-thinking":{"id":"arcee-ai/trinity-large-thinking","name":"Arcee AI: Trinity Large Thinking","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.22,"output":0.85},"limit":{"context":262144,"output":262144}},"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}},"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/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/trinity-large-preview":{"id":"arcee-ai/trinity-large-preview","name":"Arcee AI: Trinity Large Preview","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-01-28","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.45},"limit":{"context":131000,"output":32768}},"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}},"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}},"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}},"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-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}},"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-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}},"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}},"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}},"~google/gemini-pro-latest":{"id":"~google/gemini-pro-latest","name":"Google: Gemini Pro Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":12,"cache_read":0.2,"cache_write":0.375},"limit":{"context":1048576,"output":65536}},"~google/gemini-flash-latest":{"id":"~google/gemini-flash-latest","name":"Google: Gemini Flash Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05,"cache_write":0.08333333333333334},"limit":{"context":1048576,"output":65536}},"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/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}},"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/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}},"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}},"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}},"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}},"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}},"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}},"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":"2025-08-26","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"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}},"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}},"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/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-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/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-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-medium-3-5":{"id":"mistralai/mistral-medium-3-5","name":"Mistral: Mistral Medium 3.5","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-30","last_updated":"2026-05-07","modalities":{"input":["image","text"],"output":["text"]},"open_weights":false,"cost":{"input":1.5,"output":7.5},"limit":{"context":262144,"output":262144}},"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/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/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/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/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/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-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/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/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}},"mistralai/mistral-small-2603":{"id":"mistralai/mistral-small-2603","name":"Mistral: Mistral Small 4","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6,"cache_read":0.015},"limit":{"context":262144,"output":262144}},"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/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/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":{"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-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/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-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/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/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}},"~anthropic/claude-haiku-latest":{"id":"~anthropic/claude-haiku-latest","name":"Anthropic: Claude Haiku Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-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}},"~anthropic/claude-sonnet-latest":{"id":"~anthropic/claude-sonnet-latest","name":"Anthropic: Claude Sonnet Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","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":128000}},"~anthropic/claude-opus-latest":{"id":"~anthropic/claude-opus-latest","name":"Anthropic: Claude Opus Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-16","last_updated":"2026-05-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":1000000,"output":128000}},"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-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-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-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.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-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.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.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.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-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-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-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}},"x-ai/grok-4.20":{"id":"x-ai/grok-4.20","name":"xAI: Grok 4.20","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-31","last_updated":"2026-04-11","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6,"cache_read":0.2},"limit":{"context":2000000,"output":2000000}},"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.3":{"id":"x-ai/grok-4.3","name":"xAI: Grok 4.3","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2},"limit":{"context":1000000,"output":4096}},"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-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-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}},"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-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-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-4.20-multi-agent":{"id":"x-ai/grok-4.20-multi-agent","name":"xAI: Grok 4.20 Multi-Agent","attachment":true,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-31","last_updated":"2026-04-11","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6,"cache_read":0.2},"limit":{"context":2000000,"output":2000000}},"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}},"tencent/hy3-preview:free":{"id":"tencent/hy3-preview:free","name":"Tencent: Hy3 Preview (free)","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"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}},"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}},"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-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.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}},"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.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}},"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}},"microsoft/phi-4-mini-instruct":{"id":"microsoft/phi-4-mini-instruct","name":"Microsoft: Phi 4 Mini Instruct","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-10-17","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.08,"output":0.35,"cache_read":0.08},"limit":{"context":128000,"output":128000}},"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}},"poolside/laguna-m.1:free":{"id":"poolside/laguna-m.1:free","name":"Poolside: Laguna M.1 (free)","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":8192}},"poolside/laguna-xs.2:free":{"id":"poolside/laguna-xs.2:free","name":"Poolside: Laguna XS.2 (free)","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":8192}},"cohere/command-r7b-12-2024":{"id":"cohere/command-r7b-12-2024","name":"Cohere: Command R7B (12-2024)","attachment":false,"reasoning":false,"tool_call":true,"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}},"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-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}},"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}},"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-3-super-120b-a12b":{"id":"nvidia/nemotron-3-super-120b-a12b","name":"NVIDIA: Nemotron 3 Super","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-11","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.5,"cache_read":0.1},"limit":{"context":262144,"output":262144}},"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free":{"id":"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free","name":"NVIDIA: Nemotron 3 Nano Omni (free)","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-05-01","modalities":{"input":["text","audio","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":256000,"output":65536}},"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}},"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/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/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}},"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}},"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-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/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-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-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-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-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/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-chat-latest":{"id":"openai/gpt-chat-latest","name":"OpenAI: GPT Chat Latest","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"release_date":"2026-05-05","last_updated":"2026-05-07","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":400000,"output":128000}},"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-5.5":{"id":"openai/gpt-5.5","name":"OpenAI: GPT-5.5","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-24","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":1050000,"output":128000}},"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-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-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-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/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.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/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/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/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/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.4-mini":{"id":"openai/gpt-5.4-mini","name":"OpenAI: GPT-5.4 Mini","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-17","last_updated":"2026-04-11","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"output":128000}},"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/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-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"OpenAI: GPT-5.4 Nano","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-17","last_updated":"2026-04-11","modalities":{"input":["image","pdf","text"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"output":128000}},"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-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.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-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-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-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/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.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-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/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/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-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/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.4-image-2":{"id":"openai/gpt-5.4-image-2","name":"OpenAI: GPT-5.4 Image 2","attachment":true,"reasoning":true,"tool_call":false,"temperature":false,"release_date":"2026-04-21","last_updated":"2026-05-01","modalities":{"input":["image","text","pdf"],"output":["image","text"]},"open_weights":false,"cost":{"input":8,"output":15,"cache_read":2},"limit":{"context":272000,"output":128000}},"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-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-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.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-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/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}},"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/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/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-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-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-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-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-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-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":{"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/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/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.5-pro":{"id":"openai/gpt-5.5-pro","name":"OpenAI: GPT-5.5 Pro","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-24","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180},"limit":{"context":1050000,"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/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/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-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-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}},"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-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}},"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-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}},"z-ai/glm-5v-turbo":{"id":"z-ai/glm-5v-turbo","name":"Z.ai: GLM 5V Turbo","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-11","modalities":{"input":["image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":202752,"output":131072}},"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-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-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-5.1":{"id":"z-ai/glm-5.1","name":"Z.ai: GLM 5.1","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.26,"output":3.96},"limit":{"context":202752,"output":131072}},"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.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-5-turbo":{"id":"z-ai/glm-5-turbo","name":"Z.ai: GLM 5 Turbo","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-15","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":202752,"output":131072}},"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.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.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}},"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}},"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/qianfan-ocr-fast:free":{"id":"baidu/qianfan-ocr-fast:free","name":"Baidu: Qianfan-OCR-Fast (free)","attachment":true,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-05-01","modalities":{"input":["image","text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":65536,"output":28672}},"baidu/cobuddy:free":{"id":"baidu/cobuddy:free","name":"Baidu: CoBuddy (free)","attachment":false,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-05-06","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":65536}},"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":{"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}},"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-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}},"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}},"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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"MiniMax: MiniMax M2.7","family":"minimax-m2.7","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"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-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-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-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":{"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}},"~openai/gpt-latest":{"id":"~openai/gpt-latest","name":"OpenAI: GPT Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":1050000,"output":128000}},"~openai/gpt-mini-latest":{"id":"~openai/gpt-mini-latest","name":"OpenAI: GPT Mini Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"output":128000}},"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.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-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/qwen3.6-27b":{"id":"qwen/qwen3.6-27b","name":"Qwen: Qwen3.6 27B","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.325,"output":3.25},"limit":{"context":256000,"output":65536}},"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/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-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.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-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/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/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/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/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.6-35b-a3b":{"id":"qwen/qwen3.6-35b-a3b","name":"Qwen: Qwen3.6 35B A3B","attachment":true,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.1612,"output":0.96525,"cache_read":0.1612},"limit":{"context":262144,"output":65536}},"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-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-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-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}},"qwen/qwen3.6-plus":{"id":"qwen/qwen3.6-plus","name":"Qwen: Qwen3.6 Plus","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-08-26","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["text"]},"open_weights":false,"cost":{"input":0.325,"output":1.95,"cache_read":0.0325,"cache_write":0.40625},"limit":{"context":1000000,"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/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-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/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-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-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/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-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-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-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-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-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.5-plus-20260420":{"id":"qwen/qwen3.5-plus-20260420","name":"Qwen: Qwen3.5 Plus 2026-04-20","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2.4},"limit":{"context":1000000,"output":65536}},"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-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-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-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-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/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-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/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.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/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/qwen3.6-max-preview":{"id":"qwen/qwen3.6-max-preview","name":"Qwen: Qwen3.6 Max Preview","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.04,"output":6.24,"cache_write":1.3},"limit":{"context":262144,"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/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-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/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.6-flash":{"id":"qwen/qwen3.6-flash","name":"Qwen: Qwen3.6 Flash","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1.5,"cache_write":0.3125},"limit":{"context":1000000,"output":65536}},"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}},"kwaipilot/kat-coder-pro-v2":{"id":"kwaipilot/kat-coder-pro-v2","name":"Kwaipilot: KAT-Coder-Pro V2","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":256000,"output":80000}},"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/lyria-3-clip-preview":{"id":"google/lyria-3-clip-preview","name":"Google: Lyria 3 Clip Preview","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["audio","text"]},"open_weights":false,"cost":{"input":0,"output":0},"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-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-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/lyria-3-pro-preview":{"id":"google/lyria-3-pro-preview","name":"Google: Lyria 3 Pro Preview","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["audio","text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":1048576,"output":65536}},"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.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.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-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/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}},"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-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Google: Gemma 4 31B","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-11","modalities":{"input":["image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.4},"limit":{"context":262144,"output":131072}},"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/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/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-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/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/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/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/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/gemma-4-26b-a4b-it":{"id":"google/gemma-4-26b-a4b-it","name":"Google: Gemma 4 26B A4B","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-03","last_updated":"2026-04-11","modalities":{"input":["image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.12,"output":0.4},"limit":{"context":262144,"output":262144}},"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-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}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"MoonshotAI: Kimi K2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_details"},"structured_output":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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-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}},"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}},"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-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-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}},"~moonshotai/kimi-latest":{"id":"~moonshotai/kimi-latest","name":"MoonshotAI: Kimi Latest","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-27","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.74,"output":3.49,"cache_read":0.14},"limit":{"context":262142,"output":262142}},"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/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/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}},"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}},"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.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.6-fast":{"id":"anthropic/claude-opus-4.6-fast","name":"Anthropic: Claude Opus 4.6 (Fast)","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-05-31","release_date":"2026-04-07","last_updated":"2026-04-11","modalities":{"input":["image","text"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":150,"cache_read":3,"cache_write":37.5},"limit":{"context":1000000,"output":128000}},"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.6":{"id":"anthropic/claude-opus-4.6","name":"Anthropic: Claude Opus 4.6","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"],"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-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Anthropic: Claude Opus 4.7","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-16","last_updated":"2026-05-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":1000000,"output":128000}},"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.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-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-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-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-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-sonnet-4.6":{"id":"anthropic/claude-sonnet-4.6","name":"Anthropic: Claude Sonnet 4.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","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}},"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}},"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}},"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}},"xiaomi/mimo-v2.5-pro":{"id":"xiaomi/mimo-v2.5-pro","name":"Xiaomi: MiMo V2.5 Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-omni":{"id":"xiaomi/mimo-v2-omni","name":"Xiaomi: MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":65536}},"xiaomi/mimo-v2.5":{"id":"xiaomi/mimo-v2.5","name":"Xiaomi: MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-pro":{"id":"xiaomi/mimo-v2-pro","name":"Xiaomi: MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"xiaomi/mimo-v2-flash":{"id":"xiaomi/mimo-v2-flash","name":"Xiaomi: MiMo-V2-Flash","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"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.09,"output":0.29,"cache_read":0.045},"limit":{"context":262144,"output":65536}}}},"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.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-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}},"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-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}},"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}},"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,"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}},"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-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}},"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-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-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-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}},"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}},"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-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-05","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}},"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-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-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-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-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-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}},"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}},"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-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}},"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,"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}},"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.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}}}},"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}}}},"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/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/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/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/zai-org/glm-4.7-flash":{"id":"workers-ai/@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}},"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/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/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/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}},"workers-ai/@cf/nvidia/nemotron-3-120b-a12b":{"id":"workers-ai/@cf/nvidia/nemotron-3-120b-a12b","name":"Nemotron 3 Super 120B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":1.5},"limit":{"context":256000,"output":256000}},"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/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/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/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/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-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-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/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.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.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.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.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.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-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-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/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/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/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/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/moonshotai/kimi-k2.5":{"id":"workers-ai/@cf/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"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3,"cache_read":0.1},"limit":{"context":256000,"output":256000}},"workers-ai/@cf/moonshotai/kimi-k2.6":{"id":"workers-ai/@cf/moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":256000,"output":256000}},"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/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/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-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/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/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/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}},"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-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-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-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/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.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/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/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-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/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-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/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-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.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}},"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-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-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-opus-4-7":{"id":"anthropic/claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01","release_date":"2026-04-16","last_updated":"2026-04-16","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"}},"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-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-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-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-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-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":{"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-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-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,"context_over_200k":{"input":10,"output":37.5,"cache_read":1,"cache_write":12.5}},"limit":{"context":1000000,"output":128000}},"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-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}},"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-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}},"openai/gpt-5.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000}}}},"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.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":400000,"input":128000,"output":128000},"status":"deprecated"},"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"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":144000,"input":128000,"output":64000}},"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,"input":128000,"output":64000}},"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,"input":128000,"output":64000}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":false,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":400000,"input":272000,"output":128000}},"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":264000,"input":128000,"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","audio","video"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":128000,"input":128000,"output":64000},"status":"deprecated"},"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}},"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,"input":128000,"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":0,"output":0},"limit":{"context":264000,"input":128000,"output":64000}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":400000,"input":272000,"output":128000}},"claude-opus-4.7":{"id":"claude-opus-4.7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":144000,"input":128000,"output":64000}},"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}},"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":400000,"input":128000,"output":128000},"status":"deprecated"},"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":216000,"input":128000,"output":16000},"status":"deprecated"},"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,"input":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":264000,"input":128000,"output":64000},"status":"deprecated"},"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":144000,"input":128000,"output":32000}},"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},"status":"deprecated"},"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":160000,"input":128000,"output":32000}},"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-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":128000,"input":64000,"output":4096}},"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},"status":"deprecated"},"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":144000,"input":128000,"output":32000}},"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":128000,"input":64000,"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-31","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":200000,"input":128000,"output":32000}},"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":400000,"input":128000,"output":128000},"status":"deprecated"}}},"mixlayer":{"id":"mixlayer","env":["MIXLAYER_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://models.mixlayer.ai/v1","name":"Mixlayer","doc":"https://docs.mixlayer.com","models":{"qwen/qwen3.5-122b-a10b":{"id":"qwen/qwen3.5-122b-a10b","name":"Qwen3.5 122B A10B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":3.2},"limit":{"context":262144,"output":262144}},"qwen/qwen3.5-27b":{"id":"qwen/qwen3.5-27b","name":"Qwen3.5 27B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":2.4},"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-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":262144}},"qwen/qwen3.5-9b":{"id":"qwen/qwen3.5-9b","name":"Qwen3.5 9B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.4},"limit":{"context":262144,"output":262144}},"qwen/qwen3.5-35b-a3b":{"id":"qwen/qwen3.5-35b-a3b","name":"Qwen3.5 35B A3B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":1.3},"limit":{"context":262144,"output":262144}}}},"xiaomi-token-plan-sgp":{"id":"xiaomi-token-plan-sgp","env":["XIAOMI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://token-plan-sgp.xiaomimimo.com/v1","name":"Xiaomi Token Plan (Singapore)","doc":"https://platform.xiaomimimo.com/#/docs","models":{"mimo-v2-tts":{"id":"mimo-v2-tts","name":"MiMo-V2-TTS","family":"mimo","attachment":false,"reasoning":false,"tool_call":false,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["audio"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":8192,"output":16384}},"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,"output":0,"cache_read":0},"limit":{"context":262144,"output":65536}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262144,"output":131072}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}}}},"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-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_read":0.24,"cache_write":0},"limit":{"context":200000,"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-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.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-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.4,"output":4.4,"cache_read":0.26,"cache_write":0},"limit":{"context":200000,"output":131072}},"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}},"glm-5-turbo":{"id":"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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_read":0.24,"cache_write":0},"limit":{"context":200000,"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.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.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.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}}}},"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":{"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-01","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"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"}},"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"}},"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}},"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},"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}},"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.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"}},"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-08-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"}},"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-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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"}},"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},"provider":{"npm":"@ai-sdk/openai"}},"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"}},"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"}},"ring-2.6-1t-free":{"id":"ring-2.6-1t-free","name":"Ring 2.6 1T Free","family":"ring-1t-free","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-06","release_date":"2026-05-08","last_updated":"2026-05-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262000,"output":66000}},"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"}},"big-pickle":{"id":"big-pickle","name":"Big Pickle","family":"big-pickle","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":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}},"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"}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05,"cache_write":0.625},"limit":{"context":262144,"output":65536},"provider":{"npm":"@ai-sdk/anthropic"}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000},"provider":{"npm":"@ai-sdk/openai"}},"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},"status":"deprecated","provider":{"npm":"@ai-sdk/anthropic"}},"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},"status":"deprecated"},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-04","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":204800,"output":131072}},"gpt-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000},"provider":{"npm":"@ai-sdk/openai"}},"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"}},"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"}},"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"}},"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"}},"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"},"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.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"}},"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"},"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"}},"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"}},"qwen3.6-plus-free":{"id":"qwen3.6-plus-free","name":"Qwen3.6 Plus Free","family":"qwen-free","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":1048576,"output":64000},"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},"status":"deprecated"},"ling-2.6-flash-free":{"id":"ling-2.6-flash-free","name":"Ling 2.6 Flash Free","family":"ling-flash-free","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262100,"output":32800},"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},"status":"deprecated","provider":{"npm":"@ai-sdk/google"}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-10","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":65536}},"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"}},"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"},"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},"status":"deprecated"},"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"}},"hy3-preview-free":{"id":"hy3-preview-free","name":"Hy3 preview Free","family":"hy3-free","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":256000,"output":64000},"status":"deprecated"},"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}},"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"},"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"}},"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"},"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"}},"qwen3.5-plus":{"id":"qwen3.5-plus","name":"Qwen3.5 Plus","family":"qwen3.5","attachment":true,"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.2,"output":1.2,"cache_read":0.02,"cache_write":0.25},"limit":{"context":262144,"output":65536},"provider":{"npm":"@ai-sdk/anthropic"}},"mimo-v2-pro-free":{"id":"mimo-v2-pro-free","name":"MiMo V2 Pro Free","family":"mimo-pro-free","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":1048576,"output":64000},"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":204800,"output":128000}},"gpt-5.5-pro":{"id":"gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-24","last_updated":"2026-04-24","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"}},"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.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"}},"mimo-v2-omni-free":{"id":"mimo-v2-omni-free","name":"MiMo V2 Omni Free","family":"mimo-omni-free","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262144,"output":64000},"status":"deprecated"},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"provider":{"npm":"@ai-sdk/openai"}},"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},"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-05-31","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},"provider":{"npm":"@ai-sdk/anthropic"}}}},"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-2603":{"id":"step-3.5-flash-2603","name":"Step 3.5 Flash 2603","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.3,"cache_read":0.02},"limit":{"context":256000,"input":256000,"output":256000}},"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}},"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}}}},"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":{"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}},"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}},"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.5-397B-A17B":{"id":"Qwen/Qwen3.5-397B-A17B","name":"Qwen3.5-397B-A17B","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-07-15","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6,"cache_read":0.06,"cache_write":0.75},"limit":{"context":262144,"input":250000,"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-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-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}},"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-fast":{"id":"Qwen/Qwen3-235B-A22B-Thinking-2507-fast","name":"Qwen3-235B-A22B-Thinking-2507-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-07-25","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":2,"cache_read":0.05,"cache_write":0.625},"limit":{"context":8000,"input":7000,"output":8192}},"Qwen/Qwen3.5-397B-A17B-fast":{"id":"Qwen/Qwen3.5-397B-A17B-fast","name":"Qwen3.5-397B-A17B-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-07-15","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6,"cache_read":0.06,"cache_write":0.75},"limit":{"context":8000,"input":7000,"output":8192}},"Qwen/Qwen3-Next-80B-A3B-Thinking-fast":{"id":"Qwen/Qwen3-Next-80B-A3B-Thinking-fast","name":"Qwen3-Next-80B-A3B-Thinking-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-07-25","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":1.2,"cache_read":0.015,"cache_write":0.1875},"limit":{"context":8000,"input":7000,"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}},"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}},"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}},"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}},"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}},"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/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}},"nvidia/Nemotron-3-Nano-Omni":{"id":"nvidia/Nemotron-3-Nano-Omni","name":"Nemotron-3-Nano-Omni","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2025-01-20","last_updated":"2026-05-07","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":65536,"input":60000,"output":8192}},"deepseek-ai/DeepSeek-V3.2-fast":{"id":"deepseek-ai/DeepSeek-V3.2-fast","name":"DeepSeek-V3.2-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2025-01-27","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.04,"cache_write":0.5},"limit":{"context":8000,"input":7000,"output":8192}},"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}},"openai/gpt-oss-120b-fast":{"id":"openai/gpt-oss-120b-fast","name":"gpt-oss-120b-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-06","release_date":"2025-06-10","last_updated":"2026-05-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.5,"cache_read":0.01,"cache_write":0.125},"limit":{"context":8000,"input":7000,"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}},"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":{"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}},"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}},"MiniMaxAI/MiniMax-M2.5":{"id":"MiniMaxAI/MiniMax-M2.5","name":"MiniMax-M2.5","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2025-01-20","last_updated":"2026-05-07","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":196608,"input":190000,"output":8192}},"MiniMaxAI/MiniMax-M2.5-fast":{"id":"MiniMaxAI/MiniMax-M2.5-fast","name":"MiniMax-M2.5-fast","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2025-01-20","last_updated":"2026-05-07","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":8000,"input":7000,"output":8192}},"deepseek-ai/DeepSeek-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.75,"output":3.5,"cache_read":0.15},"limit":{"context":1000000,"output":384000}}}},"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":{"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}},"novita/kimi-k2.5":{"id":"novita/kimi-k2.5","name":"Kimi-K2.5","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":3,"cache_read":0.1},"limit":{"context":128000,"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},"status":"deprecated"},"novita/glm-5":{"id":"novita/glm-5","name":"GLM-5","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-02-15","last_updated":"2026-02-15","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3.2,"cache_read":0.2},"limit":{"context":205000,"output":131072}},"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/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/kimi-k2.6":{"id":"novita/kimi-k2.6","name":"Kimi-K2.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-20","last_updated":"2026-05-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.96,"output":4.04,"cache_read":0.16},"limit":{"context":262144,"input":262144,"output":262144}},"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}},"novita/deepseek-v3.2":{"id":"novita/deepseek-v3.2","name":"DeepSeek-V3.2","attachment":true,"reasoning":true,"tool_call":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.13},"limit":{"context":128000,"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/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}},"fireworks-ai/kimi-k2.5-fw":{"id":"fireworks-ai/kimi-k2.5-fw","name":"Kimi-K2.5-FW","attachment":true,"reasoning":false,"tool_call":true,"temperature":false,"release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"input":245760,"output":16384}},"empiriolabs/deepseek-v4-pro-el":{"id":"empiriolabs/deepseek-v4-pro-el","name":"DeepSeek-V4-Pro-EL","attachment":true,"reasoning":true,"tool_call":true,"release_date":"2026-04-24","last_updated":"2026-05-02","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.67,"output":3.33},"limit":{"context":1000000,"input":1000000,"output":384000}},"empiriolabs/deepseek-v4-flash-el":{"id":"empiriolabs/deepseek-v4-flash-el","name":"DeepSeek-V4-Flash-EL","attachment":true,"reasoning":true,"tool_call":true,"release_date":"2026-04-24","last_updated":"2026-05-02","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28},"limit":{"context":1000000,"input":1000000,"output":384000}},"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}},"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-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}},"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,"cost":{"input":0.35,"output":0.75},"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,"cost":{"input":0.1,"output":0.1},"limit":{"context":128000,"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},"status":"deprecated"},"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},"status":"deprecated"},"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},"status":"deprecated"},"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}},"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-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-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-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-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.20-multi-agent":{"id":"xai/grok-4.20-multi-agent","name":"Grok-4.20-Multi-Agent","attachment":true,"reasoning":false,"tool_call":true,"temperature":false,"release_date":"2026-03-13","last_updated":"2026-03-13","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6,"cache_read":0.2},"limit":{"context":128000,"output":0}},"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-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}},"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}},"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}},"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/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/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},"status":"deprecated"},"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/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/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/gpt-image-2":{"id":"openai/gpt-image-2","name":"GPT-Image-2","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"cost":{"input":5.0505,"output":32.3232,"cache_read":1.2626},"limit":{"context":0,"output":0}},"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},"status":"deprecated"},"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-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-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-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-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/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/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-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/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.4-mini":{"id":"openai/gpt-5.4-mini","name":"GPT-5.4-Mini","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-12","last_updated":"2026-03-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.68,"output":4,"cache_read":0.068},"limit":{"context":400000,"input":272000,"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/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-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"GPT-5.4-Nano","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.18,"output":1.1,"cache_read":0.018},"limit":{"context":400000,"input":272000,"output":128000}},"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/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/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.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/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/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.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/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/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/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/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-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-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.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.3-codex-spark":{"id":"openai/gpt-5.3-codex-spark","name":"GPT-5.3-Codex-Spark","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-03-04","last_updated":"2026-03-04","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":128000,"output":16384}},"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-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/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-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/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-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":{"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/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/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-image-1.5":{"id":"openai/gpt-image-1.5","name":"gpt-image-1.5","attachment":true,"reasoning":false,"tool_call":false,"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-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-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-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}},"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-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},"status":"deprecated"},"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/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}},"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/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-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/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/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/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},"status":"deprecated"},"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/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/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/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/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-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/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/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/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/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/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},"status":"deprecated"},"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}},"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/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/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/gemma-4-31b":{"id":"google/gemma-4-31b","name":"Gemma-4-31B","attachment":true,"reasoning":false,"tool_call":true,"temperature":false,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":8192}},"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/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}},"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}},"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},"status":"deprecated"},"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-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-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Claude-Opus-4.7","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-15","last_updated":"2026-04-15","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":4.3,"output":21,"cache_read":0.43,"cache_write":5.4},"limit":{"context":1048576,"output":128000}},"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.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-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}},"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-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-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-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},"status":"deprecated"},"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}},"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-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-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}},"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}},"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.5":{"id":"openai/gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"cost":{"input":4.5455,"output":27.2727,"cache_read":0.4545},"limit":{"context":400000,"output":128000}},"openai/gpt-5.5-pro":{"id":"openai/gpt-5.5-pro","name":"GPT-5.5-Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"cost":{"input":27.2727,"output":163.6364},"limit":{"context":400000,"output":128000}}}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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":{"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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}}}},"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":{"minimax-m2.7":{"id":"minimax-m2.7","name":"minimax-m2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":196608,"output":196608}},"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}},"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}},"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}},"gemma4:31b":{"id":"gemma4:31b","name":"gemma4:31b","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"output":262144}},"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}},"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":65536}},"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}},"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-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}},"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}},"gemini-3-flash-preview":{"id":"gemini-3-flash-preview","name":"gemini-3-flash-preview","family":"gemini-flash","attachment":true,"reasoning":true,"tool_call":true,"knowledge":"2025-01","release_date":"2025-12-17","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":65536}},"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}},"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}},"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}},"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}},"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}},"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}},"glm-5.1":{"id":"glm-5.1","name":"glm-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"release_date":"2026-03-27","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":202752,"output":131072}},"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}},"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}},"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}},"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}},"kimi-k2.6:cloud":{"id":"kimi-k2.6:cloud","name":"kimi-k2.6:cloud","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"output":262144}},"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}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"deepseek-v4-flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":1048576}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"deepseek-v4-pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":1048576}},"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}},"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}},"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}},"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}},"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}}}},"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-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-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"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-5-turbo":{"id":"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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":200000,"output":131072}},"glm-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":200000,"output":131072}}}},"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":{"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,"structured_output":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}},"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}},"nvidia.nemotron-super-3-120b":{"id":"nvidia.nemotron-super-3-120b","name":"NVIDIA Nemotron 3 Super 120B A12B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.65},"limit":{"context":262144,"output":131072}},"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}},"mistral.ministral-3-8b-instruct":{"id":"mistral.ministral-3-8b-instruct","name":"Ministral 3 8B","family":"ministral","attachment":false,"reasoning":false,"tool_call":true,"structured_output":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}},"au.anthropic.claude-opus-4-6-v1":{"id":"au.anthropic.claude-opus-4-6-v1","name":"AU Anthropic Claude Opus 4.6","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":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":16.5,"output":82.5,"cache_read":1.65,"cache_write":20.625},"limit":{"context":1000000,"output":128000}},"mistral.ministral-3-3b-instruct":{"id":"mistral.ministral-3-3b-instruct","name":"Ministral 3 3B","family":"ministral","attachment":false,"reasoning":false,"tool_call":true,"structured_output":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}},"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,"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},"limit":{"context":200000,"output":64000}},"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}},"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,"structured_output":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.voxtral-small-24b-2507":{"id":"mistral.voxtral-small-24b-2507","name":"Voxtral Small 24B 2507","family":"mistral","attachment":true,"reasoning":false,"tool_call":true,"structured_output":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}},"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,"structured_output":true,"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}},"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}},"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,"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}},"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}},"global.anthropic.claude-opus-4-7":{"id":"global.anthropic.claude-opus-4-7","name":"Claude Opus 4.7 (Global)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"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}},"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}},"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,"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},"limit":{"context":200000,"output":64000}},"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,"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}},"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}},"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,"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},"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,"structured_output":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}},"zai.glm-5":{"id":"zai.glm-5","name":"GLM-5","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3.2},"limit":{"context":202752,"output":101376}},"qwen.qwen3-32b-v1:0":{"id":"qwen.qwen3-32b-v1:0","name":"Qwen3 32B (dense)","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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}},"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}},"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,"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}},"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}},"us.anthropic.claude-opus-4-7":{"id":"us.anthropic.claude-opus-4-7","name":"Claude Opus 4.7 (US)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"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}},"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,"structured_output":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}},"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,"structured_output":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}},"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}},"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}},"anthropic.claude-opus-4-7":{"id":"anthropic.claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"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-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}},"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,"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}},"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}},"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-v1:0":{"id":"deepseek.v3-v1:0","name":"DeepSeek-V3.1","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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.magistral-small-2509":{"id":"mistral.magistral-small-2509","name":"Magistral Small 1.2","family":"magistral","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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}},"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}},"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,"structured_output":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}},"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,"structured_output":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-14b-instruct":{"id":"mistral.ministral-3-14b-instruct","name":"Ministral 14B 3.0","family":"ministral","attachment":false,"reasoning":false,"tool_call":true,"structured_output":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}},"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}},"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,"structured_output":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}},"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,"structured_output":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}},"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,"structured_output":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}},"eu.anthropic.claude-opus-4-7":{"id":"eu.anthropic.claude-opus-4-7","name":"Claude Opus 4.7 (EU)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"minimax.minimax-m2.5":{"id":"minimax.minimax-m2.5","name":"MiniMax M2.5","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2},"limit":{"context":196608,"output":98304}},"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}},"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}},"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,"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},"limit":{"context":200000,"output":64000}},"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,"structured_output":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}},"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}},"au.anthropic.claude-sonnet-4-6":{"id":"au.anthropic.claude-sonnet-4-6","name":"AU Anthropic Claude Sonnet 4.6","family":"claude-sonnet","attachment":true,"reasoning":true,"tool_call":true,"structured_output":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.3,"output":16.5,"cache_read":0.33,"cache_write":4.125},"limit":{"context":1000000,"output":128000}},"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,"structured_output":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}},"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,"structured_output":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}},"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}},"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}},"qwen.qwen3-coder-next":{"id":"qwen.qwen3-coder-next","name":"Qwen3 Coder Next","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-06","last_updated":"2026-02-06","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.22,"output":1.8},"limit":{"context":131072,"output":65536}},"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,"structured_output":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}},"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,"structured_output":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}},"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,"structured_output":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}},"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}},"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}},"us.deepseek.r1-v1:0":{"id":"us.deepseek.r1-v1:0","name":"DeepSeek-R1 (US)","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}},"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,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","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}},"us.meta.llama4-maverick-17b-instruct-v1:0":{"id":"us.meta.llama4-maverick-17b-instruct-v1:0","name":"Llama 4 Maverick 17B Instruct (US)","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}},"au.anthropic.claude-haiku-4-5-20251001-v1:0":{"id":"au.anthropic.claude-haiku-4-5-20251001-v1:0","name":"Claude Haiku 4.5 (AU)","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}},"jp.anthropic.claude-sonnet-4-5-20250929-v1:0":{"id":"jp.anthropic.claude-sonnet-4-5-20250929-v1:0","name":"Claude Sonnet 4.5 (JP)","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-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-31","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}},"jp.anthropic.claude-sonnet-4-6":{"id":"jp.anthropic.claude-sonnet-4-6","name":"Claude Sonnet 4.6 (JP)","family":"claude-sonnet","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08-31","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}},"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-31","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}},"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-31","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}},"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,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","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}},"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,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","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}},"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}},"au.anthropic.claude-sonnet-4-5-20250929-v1:0":{"id":"au.anthropic.claude-sonnet-4-5-20250929-v1:0","name":"Claude Sonnet 4.5 (AU)","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}},"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-31","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}},"us.meta.llama4-scout-17b-instruct-v1:0":{"id":"us.meta.llama4-scout-17b-instruct-v1:0","name":"Llama 4 Scout 17B Instruct (US)","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}},"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,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","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}},"jp.anthropic.claude-opus-4-7":{"id":"jp.anthropic.claude-opus-4-7","name":"Claude Opus 4.7 (JP)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}}}},"the-grid-ai":{"id":"the-grid-ai","env":["THEGRIDAI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.thegrid.ai/v1","name":"The Grid AI","doc":"https://thegrid.ai/docs","models":{"text-prime":{"id":"text-prime","name":"Text Prime","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":128000,"output":30000},"status":"beta"},"text-standard":{"id":"text-standard","name":"Text Standard","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-26","last_updated":"2026-02-26","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":128000,"output":16000},"status":"beta"},"text-max":{"id":"text-max","name":"Text Max","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-24","last_updated":"2026-03-24","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":1000000,"output":128000},"status":"beta"}}},"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.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}},"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}},"nvidia/Nemotron-120B-A12B":{"id":"nvidia/Nemotron-120B-A12B","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}},"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-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}},"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"},"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}},"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"},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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}},"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-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.15},"limit":{"context":1000000,"output":384000}}}},"frogbot":{"id":"frogbot","env":["FROGBOT_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://app.frogbot.ai/api/v1","name":"FrogBot","doc":"https://docs.frogbot.ai","models":{"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-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}},"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}},"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-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":200000,"output":64000}},"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}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"zai-glm-5-1":{"id":"zai-glm-5-1","name":"Z.AI GLM-5.1","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":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":198000,"output":8192}},"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}},"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}},"gpt-5-4-nano":{"id":"gpt-5-4-nano","name":"GPT-5.4 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.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"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,"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-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}},"gpt-5-5":{"id":"gpt-5-5","name":"GPT-5.5","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}},"grok-4-3":{"id":"grok-4-3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2024-11","release_date":"2026-04-30","last_updated":"2026-04-30","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2},"limit":{"context":1000000,"output":128000}},"gpt-5-4-mini":{"id":"gpt-5-4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"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-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}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek v4 Pro","family":"deepseek","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2026-01","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.74,"output":3.48,"cache_read":0.14},"limit":{"context":128000,"output":8192}},"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}},"qwen-3-6-plus":{"id":"qwen-3-6-plus","name":"Qwen 3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.1},"limit":{"context":1000000,"output":64000}},"minimax-m2-7":{"id":"minimax-m2-7","name":"MiniMax-M2.7","family":"minimax","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-09","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":192000,"output":8192}},"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}},"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}},"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}},"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}},"kimi-k2-6":{"id":"kimi-k2-6","name":"Kimi-K2.6","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.95,"output":4,"cache_read":0.16},"limit":{"context":256000,"output":128000}},"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}}}},"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-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":200000,"output":131072}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":200000,"output":131072}},"glm-5-turbo":{"id":"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-16","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"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.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}}}},"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":{"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}},"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}},"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}},"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.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","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-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}},"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}},"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}}}},"venice":{"id":"venice","env":["VENICE_API_KEY"],"npm":"venice-ai-sdk-provider","name":"Venice AI","doc":"https://docs.venice.ai","models":{"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}},"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}},"grok-4-20-multi-agent":{"id":"grok-4-20-multi-agent","name":"Grok 4.20 Multi-Agent","family":"grok","attachment":true,"reasoning":true,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2026-03-12","last_updated":"2026-05-07","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.42,"output":2.83,"cache_read":0.23,"context_over_200k":{"input":2.83,"output":5.67,"cache_read":0.45}},"limit":{"context":2000000,"output":128000}},"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}},"z-ai-glm-5v-turbo":{"id":"z-ai-glm-5v-turbo","name":"GLM 5V Turbo","family":"glmv","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.5,"output":5,"cache_read":0.3},"limit":{"context":200000,"output":32768}},"gemma-4-uncensored":{"id":"gemma-4-uncensored","name":"Gemma 4 Uncensored","family":"gemma","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-13","last_updated":"2026-04-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1625,"output":0.5},"limit":{"context":256000,"output":8192}},"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-04-09","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.23,"output":0.57,"cache_read":0.06},"limit":{"context":1000000,"output":30000}},"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,"knowledge":"2025-08-31","release_date":"2026-02-17","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3.6,"output":18,"cache_read":0.36,"cache_write":4.5},"limit":{"context":1000000,"output":64000}},"nvidia-nemotron-cascade-2-30b-a3b":{"id":"nvidia-nemotron-cascade-2-30b-a3b","name":"Nemotron Cascade 2 30B A3B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-24","last_updated":"2026-04-09","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.8},"limit":{"context":256000,"output":32768}},"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":{"id":"grok-4-20","name":"Grok 4.20","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-12","last_updated":"2026-05-07","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.42,"output":2.83,"cache_read":0.23,"context_over_200k":{"input":2.83,"output":5.67,"cache_read":0.45}},"limit":{"context":2000000,"output":128000}},"google-gemma-4-26b-a4b-it":{"id":"google-gemma-4-26b-a4b-it","name":"Google Gemma 4 26B A4B Instruct","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-12","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.1625,"output":0.5},"limit":{"context":256000,"output":8192}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"release_date":"2026-04-16","last_updated":"2026-04-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":30,"cache_read":0.6,"cache_write":7.5},"limit":{"context":1000000,"output":128000}},"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}},"qwen3-5-397b-a17b":{"id":"qwen3-5-397b-a17b","name":"Qwen 3.5 397B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-16","last_updated":"2026-04-16","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.75,"output":4.5},"limit":{"context":128000,"output":32768}},"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}},"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}},"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}},"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}},"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}},"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}},"venice-uncensored-1-2":{"id":"venice-uncensored-1-2","name":"Venice Uncensored 1.2","family":"venice","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.9},"limit":{"context":128000,"output":8192}},"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}},"mistral-small-3-2-24b-instruct":{"id":"mistral-small-3-2-24b-instruct","name":"Mistral Small 3.2 24B Instruct","family":"mistral-small","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-01-15","last_updated":"2026-03-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.09375,"output":0.25},"limit":{"context":256000,"output":16384}},"minimax-m27":{"id":"minimax-m27","name":"MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.375,"output":1.5,"cache_read":0.075},"limit":{"context":198000,"output":32768}},"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}},"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-04-16","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}},"mercury-2":{"id":"mercury-2","name":"Mercury 2","family":"mercury","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-20","last_updated":"2026-04-09","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3125,"output":0.9375,"cache_read":0.03125},"limit":{"context":128000,"output":50000}},"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}},"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}},"openai-gpt-55-pro":{"id":"openai-gpt-55-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":37.5,"output":225},"limit":{"context":1000000,"output":128000}},"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}},"venice-uncensored-role-play":{"id":"venice-uncensored-role-play","name":"Venice Role Play Uncensored","family":"venice","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-20","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":2},"limit":{"context":128000,"output":4096}},"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.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":"2024-04-01","last_updated":"2026-04-04","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.85,"output":2.75,"cache_read":0.3},"limit":{"context":198000,"output":16384}},"grok-4-3":{"id":"grok-4-3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-18","last_updated":"2026-05-04","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.42,"output":2.83,"cache_read":0.23,"context_over_200k":{"input":2.83,"output":5.67,"cache_read":0.45}},"limit":{"context":1000000,"output":32000}},"mistral-small-2603":{"id":"mistral-small-2603","name":"Mistral Small 4","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-16","last_updated":"2026-04-09","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1875,"output":0.75},"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":true,"tool_call":true,"temperature":true,"knowledge":"2025-07","release_date":"2025-11-06","last_updated":"2026-05-06","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.07,"output":0.3},"limit":{"context":128000,"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,"structured_output":true,"temperature":true,"release_date":"2025-12-06","last_updated":"2026-04-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":30,"cache_read":0.6,"cache_write":7.5},"limit":{"context":198000,"output":32768}},"qwen3-5-9b":{"id":"qwen3-5-9b","name":"Qwen 3.5 9B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-05","last_updated":"2026-04-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.15},"limit":{"context":256000,"output":32768}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-29","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.17,"output":0.35,"cache_read":0.028},"limit":{"context":1000000,"output":32768}},"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}},"openai-gpt-54-mini":{"id":"openai-gpt-54-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-31","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.9375,"output":5.625,"cache_read":0.09375},"limit":{"context":400000,"output":128000}},"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-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.34,"output":1.19,"cache_read":0.04},"limit":{"context":198000,"output":32768}},"zai-org-glm-5-1":{"id":"zai-org-glm-5-1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.75,"output":5.5,"cache_read":0.325},"limit":{"context":200000,"output":24000}},"openai-gpt-55":{"id":"openai-gpt-55","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-04-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":6.25,"output":37.5,"cache_read":0.625,"context_over_200k":{"input":12.5,"output":56.25,"cache_read":1.25}},"limit":{"context":1000000,"output":131072}},"qwen3-6-27b":{"id":"qwen3-6-27b","name":"Qwen 3.6 27B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-29","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.325,"output":3.25},"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,"knowledge":"2025-05-31","release_date":"2026-02-05","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":30,"cache_read":0.6,"cache_write":7.5},"limit":{"context":1000000,"output":128000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-29","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.73,"output":3.796,"cache_read":0.33},"limit":{"context":1000000,"output":32768}},"deepseek-v3.2":{"id":"deepseek-v3.2","name":"DeepSeek V3.2","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-10","release_date":"2025-12-04","last_updated":"2026-03-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.33,"output":0.48,"cache_read":0.16},"limit":{"context":160000,"output":32768}},"qwen-3-6-plus":{"id":"qwen-3-6-plus","name":"Qwen 3.6 Plus Uncensored","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-06","last_updated":"2026-04-12","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.625,"output":3.75,"cache_read":0.0625,"cache_write":0.78,"context_over_200k":{"input":2.5,"output":7.5,"cache_read":0.0625,"cache_write":0.78}},"limit":{"context":1000000,"output":65536}},"aion-labs-aion-2-0":{"id":"aion-labs-aion-2-0","name":"Aion 2.0","family":"o","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-24","last_updated":"2026-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":2,"cache_read":0.25},"limit":{"context":128000,"output":32768}},"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,"release_date":"2025-01-15","last_updated":"2026-04-12","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":64000}},"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}},"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}},"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-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.56,"output":3.5,"cache_read":0.22},"limit":{"context":256000,"output":65536}},"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}},"arcee-trinity-large-thinking":{"id":"arcee-trinity-large-thinking","name":"Trinity Large Thinking","family":"trinity","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-04","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3125,"output":1.125,"cache_read":0.075},"limit":{"context":256000,"output":65536}},"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}},"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}},"kimi-k2-6":{"id":"kimi-k2-6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.85,"output":4.655,"cache_read":0.22},"limit":{"context":256000,"output":65536}},"claude-opus-4-6-fast":{"id":"claude-opus-4-6-fast","name":"Claude Opus 4.6 Fast","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-05-31","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":36,"output":180,"cache_read":3.6,"cache_write":45},"limit":{"context":1000000,"output":128000}},"z-ai-glm-5-turbo":{"id":"z-ai-glm-5-turbo","name":"GLM 5 Turbo","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-15","last_updated":"2026-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":200000,"output":32768}},"google-gemma-4-31b-it":{"id":"google-gemma-4-31b-it","name":"Google Gemma 4 31B Instruct","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-03","last_updated":"2026-04-12","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.175,"output":0.5},"limit":{"context":256000,"output":8192}}}},"aihubmix":{"id":"aihubmix","env":["AIHUBMIX_API_KEY"],"npm":"@aihubmix/ai-sdk-provider","name":"AIHubMix","doc":"https://docs.aihubmix.com","models":{"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2958,"output":1.1832,"cache_read":0.05916},"limit":{"context":200000,"output":128000}},"coding-glm-5.1-free":{"id":"coding-glm-5.1-free","name":"Coding GLM 5.1 (free)","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-11","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":204800,"output":128000}},"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","audio","video","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}},"kimi-k2.5":{"id":"kimi-k2.5","name":"Kimi K2.5","family":"kimi-k2.5","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.105},"limit":{"context":256000,"output":0}},"glm-5v-turbo":{"id":"glm-5v-turbo","name":"GLM 5 Vision Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-05-09","last_updated":"2026-05-09","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.7042,"output":3.09848,"cache_read":0.169008},"limit":{"context":200000,"output":128000}},"grok-4.3":{"id":"grok-4.3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2,"context_over_200k":{"input":2.5,"output":5,"cache_read":0.4}},"limit":{"context":1000000,"output":1000000}},"coding-minimax-m2.7-highspeed":{"id":"coding-minimax-m2.7-highspeed","name":"Coding MiniMax M2.7 Highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":204800,"output":13100}},"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-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":200000,"output":64000}},"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","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":12,"cache_read":0.2},"limit":{"context":1048576,"output":65536}},"coding-glm-5.1":{"id":"coding-glm-5.1","name":"Coding-GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-11","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.06,"output":0.22},"limit":{"context":200000,"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","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.05},"limit":{"context":1048576,"output":65536}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":1050000,"output":128000}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"deepseek-v4-flash-think":{"id":"deepseek-v4-flash-think","name":"DeepSeek V4 Flash Think","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.154,"output":0.308,"cache_read":0.0308},"limit":{"context":1000000,"output":384000}},"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}},"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.125},"limit":{"context":1048576,"output":65536}},"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}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-05-09","last_updated":"2026-05-09","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.282,"output":1.692,"cache_read":0.0282,"cache_write":0.3525},"limit":{"context":991000,"output":64000}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4-Mini","family":"gpt-mini","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":false,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"output":128000}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.845,"output":3.38,"cache_read":0.183112},"limit":{"context":200000,"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.275},"limit":{"context":200000,"output":100000}},"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,"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-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.499,"cache_read":0.03},"limit":{"context":1048576,"output":65536}},"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}},"gemini-3.1-flash-lite":{"id":"gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","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","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1.5,"cache_read":0.25},"limit":{"context":1048576,"output":65536}},"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}},"claude-opus-4-6-think":{"id":"claude-opus-4-6-think","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-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":200000,"output":32000}},"coding-minimax-m2.7-free":{"id":"coding-minimax-m2.7-free","name":"Coding-MiniMax-M2.7-Free","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":204800,"output":13100}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.154,"output":0.308,"cache_read":0.0308},"limit":{"context":1000000,"output":384000}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":3.9995,"cache_read":0.160835},"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,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":15,"cache_read":0.25},"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-31","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}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.478,"output":0.956,"cache_read":0.004302},"limit":{"context":1000000,"output":384000}},"claude-opus-4-7-think":{"id":"claude-opus-4-7-think","name":"Claude Opus 4.7 Thinking","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"coding-minimax-m2.7":{"id":"coding-minimax-m2.7","name":"Coding MiniMax M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":204800,"output":13100}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-05-09","last_updated":"2026-05-09","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.268,"output":7.608,"cache_read":0.1268,"cache_write":1.585},"limit":{"context":240000,"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":2,"output":8,"cache_read":0.5},"limit":{"context":1047576,"output":32768}},"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}},"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}},"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-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":200000,"output":64000}},"qwen3.6-flash":{"id":"qwen3.6-flash","name":"Qwen3.6 Flash","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.169,"output":1.014,"cache_read":0.0169,"cache_write":0.21125},"limit":{"context":991000,"output":64000}}}},"cerebras":{"id":"cerebras","env":["CEREBRAS_API_KEY"],"npm":"@ai-sdk/cerebras","name":"Cerebras","doc":"https://inference-docs.cerebras.ai/models/overview","models":{"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}},"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}},"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}},"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}}}},"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":{"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}},"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}},"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}}}},"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":{"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}},"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}}}},"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-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-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-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.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"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.5":{"id":"kimi-k2.5","name":"Kimi K2.5","family":"kimi-k2.5","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}}}},"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-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-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-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"}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":false,"reasoning":true,"tool_call":true,"interleaved":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4},"limit":{"context":262144,"output":262144},"provider":{"npm":"@ai-sdk/openai-compatible","api":"https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/models","shape":"completions"}},"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,"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-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"}},"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}},"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}},"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-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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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-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}},"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}},"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":{"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}},"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":{"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}},"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}},"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-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}},"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}},"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}},"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}},"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}},"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.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-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}},"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-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.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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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-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-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}},"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}},"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}},"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-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}},"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}},"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}},"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"}},"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}},"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}},"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}},"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}},"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}},"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}},"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.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}},"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-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000}},"gpt-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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}},"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-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-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}},"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}},"cohere-command-r-plus-08-2024":{"id":"cohere-command-r-plus-08-2024","name":"Command R+","family":"command-r","attachment":false,"reasoning":false,"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-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}},"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}},"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}},"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.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}},"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}},"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-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}},"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}},"cohere-command-r-08-2024":{"id":"cohere-command-r-08-2024","name":"Command R","family":"command-r","attachment":false,"reasoning":false,"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-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}},"gpt-4-turbo":{"id":"gpt-4-turbo","name":"GPT-4 Turbo","family":"gpt","attachment":true,"reasoning":false,"tool_call":true,"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.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000}}}},"abliteration-ai":{"id":"abliteration-ai","env":["ABLIT_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.abliteration.ai/v1","name":"abliteration.ai","doc":"https://docs.abliteration.ai/models","models":{"abliterated-model":{"id":"abliterated-model","name":"Abliterated Model","attachment":true,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2026-01-06","last_updated":"2026-01-06","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":3,"output":3},"limit":{"context":150000,"input":150000,"output":8192}}}},"wafer.ai":{"id":"wafer.ai","env":["WAFER_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://pass.wafer.ai/v1","name":"Wafer","doc":"https://docs.wafer.ai/wafer-pass","models":{"Qwen3.5-397B-A17B":{"id":"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,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":262144,"output":65536}},"GLM-5.1":{"id":"GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":202752,"output":131072}},"MiniMax-M2.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":204800,"output":131072}},"DeepSeek-V4-Pro":{"id":"DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":1000000,"output":384000}}}},"cohere":{"id":"cohere","env":["COHERE_API_KEY"],"npm":"@ai-sdk/cohere","name":"Cohere","doc":"https://docs.cohere.com/docs/models","models":{"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}},"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}},"c4ai-aya-vision-8b":{"id":"c4ai-aya-vision-8b","name":"Aya Vision 8B","attachment":true,"reasoning":false,"tool_call":false,"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-r-plus-08-2024":{"id":"command-r-plus-08-2024","name":"Command R+","family":"command-r","attachment":false,"reasoning":false,"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}},"c4ai-aya-expanse-8b":{"id":"c4ai-aya-expanse-8b","name":"Aya Expanse 8B","attachment":false,"reasoning":false,"tool_call":false,"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}},"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-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}},"c4ai-aya-vision-32b":{"id":"c4ai-aya-vision-32b","name":"Aya Vision 32B","attachment":true,"reasoning":false,"tool_call":false,"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-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":false,"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}},"c4ai-aya-expanse-32b":{"id":"c4ai-aya-expanse-32b","name":"Aya Expanse 32B","attachment":false,"reasoning":false,"tool_call":false,"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":false,"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}}}},"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":{"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}},"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}},"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}},"MiniMaxAI/MiniMax-M2.5":{"id":"MiniMaxAI/MiniMax-M2.5","name":"MiniMax-M2.5","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":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,"input":180000,"output":16000}}}},"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}}}},"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-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-4.3":{"id":"grok-4.3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2,"context_over_200k":{"input":2.5,"output":5,"cache_read":0.4}},"limit":{"context":1000000,"output":30000}},"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-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-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-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-4.20-0309-reasoning":{"id":"grok-4.20-0309-reasoning","name":"Grok 4.20 (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}},"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-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-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-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-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-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-4.20-0309-non-reasoning":{"id":"grok-4.20-0309-non-reasoning","name":"Grok 4.20 (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}},"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-4.20-multi-agent-0309":{"id":"grok-4.20-multi-agent-0309","name":"Grok 4.20 Multi-Agent","family":"grok","attachment":true,"reasoning":true,"tool_call":false,"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}},"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-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-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-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-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-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-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-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-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}}}},"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":{"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/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/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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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.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}},"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}},"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}},"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}}}},"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-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-31","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,"context_over_200k":{"input":6,"output":22.5,"cache_read":0.6,"cache_write":7.5}},"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-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-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-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-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-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-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-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-31","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,"context_over_200k":{"input":10,"output":37.5,"cache_read":1,"cache_write":12.5}},"limit":{"context":1000000,"output":128000}},"claude-opus-4-7@default":{"id":"claude-opus-4-7@default","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}}}},"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":{"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}},"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-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}},"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/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/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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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: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-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.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-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.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-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: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.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.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:Qwen/Qwen3.5-397B-A17B":{"id":"hf:Qwen/Qwen3.5-397B-A17B","name":"Qwen3.5-97B-A17B","family":"qwen","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-11","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3,"cache_read":0.6},"limit":{"context":262144,"output":65536},"status":"beta"},"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-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}},"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/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: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-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-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.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.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: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: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: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:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4":{"id":"hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4","name":"Nemotron 3 Super 120B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-03-11","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1,"cache_read":0.3},"limit":{"context":262144,"output":65536}},"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: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.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:zai-org/GLM-5.1":{"id":"hf:zai-org/GLM-5.1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-04-12","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":1},"limit":{"context":196608,"output":65536}},"hf:zai-org/GLM-5":{"id":"hf: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-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":1},"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:moonshotai/Kimi-K2.6":{"id":"hf:moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.95},"limit":{"context":262144,"output":65536}}}},"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":{"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}},"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}},"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-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/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}},"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/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/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}},"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.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-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-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}},"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-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-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}},"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning":{"id":"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning","name":"Nemotron 3 Nano Omni","family":"nemotron","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-28","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":256000,"output":65536}},"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/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/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/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/nemotron-3-super-120b-a12b":{"id":"nvidia/nemotron-3-super-120b-a12b","name":"Nemotron 3 Super","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.8},"limit":{"context":262144,"output":262144}},"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/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}},"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/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/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/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.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/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/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}},"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":{"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-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}},"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-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}},"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}},"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}},"minimaxai/minimax-m2.7":{"id":"minimaxai/minimax-m2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-04-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"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,"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}},"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}},"z-ai/glm-5.1":{"id":"z-ai/glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":131072}},"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.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/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/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/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-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/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.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-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}},"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}},"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/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/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.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/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/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/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}},"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}},"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-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-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/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-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Gemma-4-31B-IT","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":256000,"output":16384}},"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/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-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/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-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}},"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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"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-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}},"mistralai/mistral-medium-3.5-128b":{"id":"mistralai/mistral-medium-3.5-128b","name":"Mistral Medium 3.5 128B","family":"mistral-medium","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-29","last_updated":"2026-04-29","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":262144,"output":262144}},"deepseek-ai/deepseek-v4-flash":{"id":"deepseek-ai/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1048576,"output":393216}},"deepseek-ai/deepseek-v4-pro":{"id":"deepseek-ai/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1048576,"output":393216}}}},"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}},"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-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.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.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}},"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}},"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}},"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}},"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}}}},"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-edit-2":{"id":"mercury-edit-2","name":"Mercury Edit 2","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","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-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}}}},"openai":{"id":"openai","env":["OPENAI_API_KEY"],"npm":"@ai-sdk/openai","name":"OpenAI","doc":"https://platform.openai.com/docs/models","models":{"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}},"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-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}},"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-5.3-chat-latest":{"id":"gpt-5.3-chat-latest","name":"GPT-5.3 Chat (latest)","family":"gpt","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"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}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"experimental":{"modes":{"fast":{"cost":{"input":12.5,"output":75,"cache_read":1.25},"provider":{"body":{"service_tier":"priority"}}}}}},"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-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}},"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-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}},"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.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}},"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-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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6,"cache_read":0.08},"limit":{"context":128000,"output":16384}},"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.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000},"experimental":{"modes":{"fast":{"cost":{"input":1.5,"output":9,"cache_read":0.15},"provider":{"body":{"service_tier":"priority"}}}}}},"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-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000}},"gpt-image-1":{"id":"gpt-image-1","name":"gpt-image-1","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-04-24","last_updated":"2025-04-24","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"limit":{"context":0,"input":0,"output":0}},"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}},"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.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}},"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-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.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}},"gpt-image-1-mini":{"id":"gpt-image-1-mini","name":"gpt-image-1-mini","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-09-26","last_updated":"2025-09-26","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"limit":{"context":0,"input":0,"output":0}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":60,"cache_read":7.5},"limit":{"context":200000,"output":100000}},"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}},"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}},"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}},"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}},"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}},"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-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-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.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},"experimental":{"modes":{"fast":{"cost":{"input":5,"output":30,"cache_read":0.5},"provider":{"body":{"service_tier":"priority"}}}}}},"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-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}},"chatgpt-image-latest":{"id":"chatgpt-image-latest","name":"chatgpt-image-latest","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-12-16","last_updated":"2025-12-16","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"limit":{"context":0,"input":0,"output":0}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":200000,"output":100000}},"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":{"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10,"cache_read":1.25},"limit":{"context":128000,"output":16384}},"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}},"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}},"gpt-image-1.5":{"id":"gpt-image-1.5","name":"gpt-image-1.5","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-11-25","last_updated":"2025-11-25","modalities":{"input":["text","image"],"output":["text","image"]},"open_weights":false,"limit":{"context":0,"input":0,"output":0}},"gpt-5.5-pro":{"id":"gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":1047576,"output":32768}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":1.6,"cache_read":0.1},"limit":{"context":1047576,"output":32768}},"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-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}}}},"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":{"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}},"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-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.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}},"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-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-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/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-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-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-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":{"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.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-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":{"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/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.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}},"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-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}},"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-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,"knowledge":"2025-08-31","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-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-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-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-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-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}},"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.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-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-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-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}},"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.31,"cache_write":2.375,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"limit":{"context":1048576,"output":65536}},"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}},"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}}}},"digitalocean":{"id":"digitalocean","env":["DIGITALOCEAN_ACCESS_TOKEN"],"npm":"@ai-sdk/openai-compatible","api":"https://inference.do-ai.run/v1","name":"DigitalOcean","doc":"https://docs.digitalocean.com/products/gradient-ai-platform/details/models/","models":{"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6,"cache_read":0.075},"limit":{"context":128000,"output":16384}},"multi-qa-mpnet-base-dot-v1":{"id":"multi-qa-mpnet-base-dot-v1","name":"Multi-QA-mpnet-base-dot-v1","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2021-08-30","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.009,"output":0},"limit":{"context":512,"output":768}},"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-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":2.7},"limit":{"context":262144,"output":32768}},"nemotron-3-nano-omni":{"id":"nemotron-3-nano-omni","name":"Nemotron Nano 3 Omni","family":"nemotron","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-28","last_updated":"2026-04-30","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":0.9},"limit":{"context":65536,"output":65536}},"llama3-8b-instruct":{"id":"llama3-8b-instruct","name":"Llama 3.1 Instruct (8B)","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.198,"output":0.198},"limit":{"context":131072,"output":131072}},"anthropic-claude-opus-4.7":{"id":"anthropic-claude-opus-4.7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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":{"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.3,"cache_write":3.75}},"limit":{"context":1000000,"output":64000}},"wan2-2-t2v-a14b":{"id":"wan2-2-t2v-a14b","name":"Wan2.2-T2V-A14B","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-07-28","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["video"]},"open_weights":true,"cost":{"input":0.6,"output":0},"limit":{"context":100,"output":1}},"qwen-2.5-14b-instruct":{"id":"qwen-2.5-14b-instruct","name":"Qwen 2.5 14B Instruct","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-09","release_date":"2024-09-19","last_updated":"2024-09-19","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":131072,"output":131072}},"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":1000000,"output":128000}},"qwen3.5-397b-a17b":{"id":"qwen3.5-397b-a17b","name":"Qwen 3.5 397B A17B","family":"qwen3.5","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-15","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.55,"output":3.5},"limit":{"context":262144,"output":81920}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":200000,"output":100000}},"e5-large-v2":{"id":"e5-large-v2","name":"E5 Large v2","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2023-05-19","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.02,"output":0},"limit":{"context":512,"output":1024}},"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,"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}},"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-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3.2},"limit":{"context":202752,"output":128000}},"openai-gpt-5.4-nano":{"id":"openai-gpt-5.4-nano","name":"GPT-5.4 nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"output":128000}},"mistral-7b-instruct-v0.3":{"id":"mistral-7b-instruct-v0.3","name":"Mistral 7B Instruct v0.3","family":"mistral","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2024-05-22","last_updated":"2024-05-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":32768,"output":32768}},"llama3.3-70b-instruct":{"id":"llama3.3-70b-instruct","name":"Llama 3.3 Instruct 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.65,"output":0.65},"limit":{"context":128000,"output":128000}},"mistral-3-14B":{"id":"mistral-3-14B","name":"Ministral 3 14B Instruct","family":"ministral","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-15","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.2},"limit":{"context":262144,"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,"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.99,"output":0.99},"limit":{"context":131072,"output":32768}},"alibaba-qwen3-32b":{"id":"alibaba-qwen3-32b","name":"Qwen3-32B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-30","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":0.55},"limit":{"context":131000,"output":40960}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":60,"cache_read":7.5},"limit":{"context":200000,"output":100000}},"anthropic-claude-3-opus":{"id":"anthropic-claude-3-opus","name":"Claude 3 Opus","family":"claude-opus","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2023-08","release_date":"2024-02-29","last_updated":"2024-02-29","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":4096},"status":"deprecated"},"stable-diffusion-3.5-large":{"id":"stable-diffusion-3.5-large","name":"Stable Diffusion 3.5 Large","family":"stable-diffusion","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-10-22","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["image"]},"open_weights":true,"cost":{"input":0.08,"output":0},"limit":{"context":256,"output":1}},"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,"output":128000}},"llama-4-maverick":{"id":"llama-4-maverick","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-05","last_updated":"2026-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":0.87},"limit":{"context":1000000,"output":16384}},"anthropic-claude-4.5-sonnet":{"id":"anthropic-claude-4.5-sonnet","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,"context_over_200k":{"input":6,"output":22.5,"cache_read":0.3,"cache_write":3.75}},"limit":{"context":1000000,"output":64000}},"qwen3-embedding-0.6b":{"id":"qwen3-embedding-0.6b","name":"Qwen3 Embedding 0.6B","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-06-03","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.04,"output":0},"limit":{"context":8000,"output":1024},"status":"beta"},"anthropic-claude-4.5-haiku":{"id":"anthropic-claude-4.5-haiku","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":1,"output":5,"cache_read":1,"cache_write":1.25},"limit":{"context":200000,"output":64000}},"gte-large-en-v1.5":{"id":"gte-large-en-v1.5","name":"GTE Large (v1.5)","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-03-27","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.09,"output":0},"limit":{"context":8192,"output":1024}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":1047576,"output":32768}},"anthropic-claude-3.5-haiku":{"id":"anthropic-claude-3.5-haiku","name":"Claude 3.5 Haiku","family":"claude-haiku","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-07","release_date":"2024-11-05","last_updated":"2024-11-05","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":4,"cache_read":0.08,"cache_write":1},"limit":{"context":200000,"output":8192},"status":"deprecated"},"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}},"deepseek-3.2":{"id":"deepseek-3.2","name":"DeepSeek V3.2","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2025-12-02","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":1.6},"limit":{"context":128000,"output":64000}},"nemotron-3-nano-30b":{"id":"nemotron-3-nano-30b","name":"Nemotron 3 Nano 30B A3B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-04-14","last_updated":"2025-04-14","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"output":262144}},"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}},"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,"knowledge":"2024-06","release_date":"2025-08-05","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.05,"output":0.45},"limit":{"context":131072,"output":131072}},"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":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.45,"output":1.7},"limit":{"context":262144,"output":65536}},"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-oss-120b":{"id":"openai-gpt-oss-120b","name":"gpt-oss-120b","family":"gpt-oss","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-06","release_date":"2025-08-05","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.7},"limit":{"context":131072,"output":131072}},"gemma-4-31B-it":{"id":"gemma-4-31B-it","name":"Gemma 4 31B","family":"gemma","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.18,"output":0.5},"limit":{"context":256000,"output":8192}},"nemotron-nano-12b-v2-vl":{"id":"nemotron-nano-12b-v2-vl","name":"Nemotron Nano 12B v2 VL","family":"nemotron","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-10","release_date":"2025-12-01","last_updated":"2026-04-30","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.6},"limit":{"context":128000,"output":16384}},"anthropic-claude-4.1-opus":{"id":"anthropic-claude-4.1-opus","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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":false,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4},"limit":{"context":262144,"output":262144}},"openai-gpt-image-2":{"id":"openai-gpt-image-2","name":"GPT Image 2","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-04-24","last_updated":"2025-04-24","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"limit":{"context":0,"output":0}},"anthropic-claude-4.6-sonnet":{"id":"anthropic-claude-4.6-sonnet","name":"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-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,"context_over_200k":{"input":6,"output":22.5,"cache_read":0.3,"cache_write":3.75}},"limit":{"context":1000000,"output":64000}},"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,"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":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":1,"cache_write":1.25},"limit":{"context":200000,"output":64000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48},"limit":{"context":1048576,"output":393216}},"ministral-3-8b-instruct-2512":{"id":"ministral-3-8b-instruct-2512","name":"Ministral 3 8B","family":"ministral","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-12-15","last_updated":"2025-12-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"output":262144}},"minimax-m2.5":{"id":"minimax-m2.5","name":"MiniMax M2.5","family":"minimax-m2.5","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-08","release_date":"2026-02-12","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2},"limit":{"context":204800,"output":128000},"status":"beta"},"openai-gpt-image-1":{"id":"openai-gpt-image-1","name":"GPT Image 1","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-04-24","last_updated":"2025-04-24","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"cost":{"input":5,"output":40,"cache_read":1.25},"limit":{"context":0,"output":0}},"openai-gpt-5.5":{"id":"openai-gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-30","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1000000,"output":128000}},"nvidia-nemotron-3-super-120b":{"id":"nvidia-nemotron-3-super-120b","name":"Nemotron-3-Super-120B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2026-02","release_date":"2026-03-11","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.65},"limit":{"context":256000,"output":32768},"status":"beta"},"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,"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,"output":128000}},"all-mini-lm-l6-v2":{"id":"all-mini-lm-l6-v2","name":"All-MiniLM-L6-v2","family":"text-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2021-08-30","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.009,"output":0},"limit":{"context":256,"output":384}},"bge-m3":{"id":"bge-m3","name":"BGE M3","family":"bge","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-01-30","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.02,"output":0},"limit":{"context":8192,"output":1024}},"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":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}},"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-31","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,"context_over_200k":{"input":10,"output":37.5,"cache_read":0.5,"cache_write":6.25}},"limit":{"context":1000000,"output":128000}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10,"cache_read":1.25},"limit":{"context":128000,"output":16384}},"openai-gpt-5.4-mini":{"id":"openai-gpt-5.4-mini","name":"GPT-5.4 mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"output":128000}},"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,"output":128000}},"arcee-trinity-large-thinking":{"id":"arcee-trinity-large-thinking","name":"Trinity Large Thinking","family":"trinity","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":0.9,"cache_read":0.06},"limit":{"context":256000,"output":128000},"status":"beta"},"mistral-nemo-instruct-2407":{"id":"mistral-nemo-instruct-2407","name":"Mistral Nemo Instruct","family":"mistral","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2024-07-18","last_updated":"2024-07-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.3},"limit":{"context":128000,"output":16384},"status":"deprecated"},"deepseek-v3":{"id":"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":"2025-03-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":163840,"output":131072}},"bge-reranker-v2-m3":{"id":"bge-reranker-v2-m3","name":"BGE Reranker v2 M3","family":"bge","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-03-12","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.01,"output":0},"limit":{"context":8192,"output":1}},"anthropic-claude-3.7-sonnet":{"id":"anthropic-claude-3.7-sonnet","name":"Claude 3.7 Sonnet","family":"claude-sonnet","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2024-11","release_date":"2025-02-24","last_updated":"2025-02-24","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15,"cache_read":0.3,"cache_write":3.75},"limit":{"context":200000,"output":64000},"status":"deprecated"},"qwen3-tts-voicedesign":{"id":"qwen3-tts-voicedesign","name":"Qwen3 TTS VoiceDesign","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-04-21","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["audio"]},"open_weights":true,"limit":{"context":32768,"output":1}},"openai-gpt-image-1.5":{"id":"openai-gpt-image-1.5","name":"GPT Image 1.5","family":"gpt-image","attachment":true,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-11-25","last_updated":"2025-11-25","modalities":{"input":["text","image"],"output":["image"]},"open_weights":false,"cost":{"input":5,"output":10,"cache_read":1},"limit":{"context":0,"output":0}},"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,"output":128000}},"anthropic-claude-3.5-sonnet":{"id":"anthropic-claude-3.5-sonnet","name":"Claude 3.5 Sonnet","family":"claude-sonnet","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2024-06-20","last_updated":"2024-10-22","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15,"cache_read":0.3,"cache_write":3.75},"limit":{"context":200000,"output":8192},"status":"deprecated"},"fal-ai/fast-sdxl":{"id":"fal-ai/fast-sdxl","name":"Fast SDXL","family":"stable-diffusion","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2023-07-26","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["image"]},"open_weights":true,"limit":{"context":0,"output":0}},"fal-ai/flux/schnell":{"id":"fal-ai/flux/schnell","name":"FLUX.1 [schnell]","family":"flux","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2024-08-01","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["image"]},"open_weights":true,"limit":{"context":0,"output":0}},"fal-ai/elevenlabs/tts/multilingual-v2":{"id":"fal-ai/elevenlabs/tts/multilingual-v2","name":"ElevenLabs Multilingual TTS v2","family":"elevenlabs","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2023-08-22","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["audio"]},"open_weights":false,"limit":{"context":0,"output":0}},"fal-ai/stable-audio-25/text-to-audio":{"id":"fal-ai/stable-audio-25/text-to-audio","name":"Stable Audio 2.5 (Text-to-Audio)","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2025-10-08","last_updated":"2026-04-16","modalities":{"input":["text"],"output":["audio"]},"open_weights":false,"limit":{"context":0,"output":0}}}},"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":{"MiniMax-M2.5":{"id":"MiniMax-M2.5","name":"MiniMax M2.5","family":"minimax","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-09","release_date":"2025-02-11","last_updated":"2025-02-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2},"limit":{"context":194000,"output":4096}},"GLM-5-FP8":{"id":"GLM-5-FP8","name":"GLM 5 FP8","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-02-11","last_updated":"2026-02-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.85,"output":3.1},"limit":{"context":200000,"output":131072}},"DeepSeek-V3.2":{"id":"DeepSeek-V3.2","name":"DeepSeek V3.2","family":"deepseek","attachment":false,"reasoning":false,"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.55,"output":1.65},"limit":{"context":127000,"output":4096}},"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-06","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":129000,"output":4096}},"Kimi-K2.5":{"id":"Kimi-K2.5","name":"Kimi K2 Instruct","family":"kimi","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-04","release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.55,"output":2.75},"limit":{"context":254000,"output":32768}}}},"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":{"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}},"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}},"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}},"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.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","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-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}},"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}},"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}}}},"mistral":{"id":"mistral","env":["MISTRAL_API_KEY"],"npm":"@ai-sdk/mistral","name":"Mistral","doc":"https://docs.mistral.ai/getting-started/models/","models":{"mistral-small-latest":{"id":"mistral-small-latest","name":"Mistral Small (latest)","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":256000,"output":256000}},"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}},"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}},"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-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}},"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}},"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-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}},"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}},"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-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}},"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}},"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}},"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}},"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-small-2603":{"id":"mistral-small-2603","name":"Mistral Small 4","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":256000,"output":256000}},"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}},"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-2604":{"id":"mistral-medium-2604","name":"Mistral Medium 3.5","family":"mistral-medium","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-29","last_updated":"2026-04-29","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":1.5,"output":7.5},"limit":{"context":262144,"output":262144}},"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}},"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}},"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}},"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}},"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}},"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}},"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-medium-latest":{"id":"mistral-medium-latest","name":"Mistral Medium (latest)","family":"mistral-medium","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-29","last_updated":"2026-04-29","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":1.5,"output":7.5},"limit":{"context":262144,"output":262144}}}},"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}},"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-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}},"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}},"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.5-9b":{"id":"qwen3.5-9b","name":"Qwen3.5-9B","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-15","last_updated":"2026-02-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.15},"limit":{"context":262144,"output":262144}},"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}},"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}},"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}}}},"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":{"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}},"zai-org/GLM-5.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"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,"cache_read":0.5},"limit":{"context":202752,"output":202752}},"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}},"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}},"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,"cache_read":0.06},"limit":{"context":196608,"output":196608}}}},"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":{"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax-m2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.47,"output":1.4},"limit":{"context":202752,"output":196072}},"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.09,"output":5.43},"limit":{"context":200000,"output":200000}},"qwen3-235b-a22b-instruct-2507":{"id":"qwen3-235b-a22b-instruct-2507","name":"Qwen3 235B A22B Instruct 2507","family":"qwen","attachment":false,"reasoning":true,"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.062,"output":0.408},"limit":{"context":131000,"output":131000}},"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}},"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}},"claude-opus4-7":{"id":"claude-opus4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5.6,"output":27.99,"cache_read":0.56,"cache_write":6.99},"limit":{"context":1000000,"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":true,"cost":{"input":1.08,"output":3.44},"limit":{"context":202752,"output":202752}},"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}},"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}},"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}},"codestral-2508":{"id":"codestral-2508","name":"Codestral 2508","family":"mistral","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-03","release_date":"2025-07-30","last_updated":"2025-07-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.9,"cache_read":0.03},"limit":{"context":256000,"output":256000}},"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}},"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}},"nemotron-3-super-120b-a12b":{"id":"nemotron-3-super-120b-a12b","name":"Nemotron 3 Super 120B A12B","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-12","release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.266,"output":0.799},"limit":{"context":262144,"output":262144}},"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}},"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}},"claude-opus4-6":{"id":"claude-opus4-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-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5.98,"output":29.89},"limit":{"context":1000000,"output":1000000}},"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}},"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}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-14","last_updated":"2026-04-14","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.31,"output":4.1,"cache_read":0.24},"limit":{"context":204800,"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}},"claude-opus4-5":{"id":"claude-opus4-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.98,"output":29.89},"limit":{"context":200000,"output":200000}},"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}},"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}},"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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.81,"output":3.54,"cache_read":0.2},"limit":{"context":256000,"output":256000}},"qwen3-coder-next":{"id":"qwen3-coder-next","name":"Qwen3 Coder Next 80B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-02-04","last_updated":"2026-02-04","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.158,"output":0.84},"limit":{"context":256000,"output":65536}},"claude-4-6-sonnet":{"id":"claude-4-6-sonnet","name":"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-03-13","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":3.59,"output":17.92},"limit":{"context":1000000,"output":1000000}},"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}},"mixtral-8x7B-instruct-v0.1":{"id":"mixtral-8x7B-instruct-v0.1","name":"Mixtral 8x7B Instruct v0.1","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"knowledge":"2023-09","release_date":"2023-12-11","last_updated":"2023-12-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.438,"output":0.68},"limit":{"context":32000,"output":32000}},"hermes-4-70b":{"id":"hermes-4-70b","name":"Hermes 4 70B","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2023-12","release_date":"2025-08-26","last_updated":"2025-08-26","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.116,"output":0.358},"limit":{"context":128000,"output":128000}},"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.32,"output":1.18},"limit":{"context":196608,"output":196608}},"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.266,"output":0.444},"limit":{"context":163840,"output":163840}},"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}},"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}},"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}},"qwen-2.5-72b-instruct":{"id":"qwen-2.5-72b-instruct","name":"Qwen2.5 72B Instruct","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2024-06","release_date":"2024-09-19","last_updated":"2024-09-19","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.062,"output":0.231},"limit":{"context":33000,"output":33000}},"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":0.585,"output":2.307},"limit":{"context":164000,"output":164000}},"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}},"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}},"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}},"qwen3.5-122b-a10b":{"id":"qwen3.5-122b-a10b","name":"Qwen3.5 122B A10B","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2026-01","release_date":"2026-02-24","last_updated":"2026-02-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.444,"output":3.106},"limit":{"context":262144,"output":262144}},"llama-3.3-70b-instruct":{"id":"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.089,"output":0.275},"limit":{"context":131000,"output":131000}},"mistral-large-2512":{"id":"mistral-large-2512","name":"Mistral Large 3 2512","family":"mistral-large","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-12","release_date":"2025-12-01","last_updated":"2025-12-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":1.5,"cache_read":0.05},"limit":{"context":256000,"output":256000}},"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":"2026-01","release_date":"2026-02-16","last_updated":"2026-02-16","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":250000,"output":250000}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.133,"output":0.266,"cache_read":0.028},"limit":{"context":1048576,"output":384000}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.553,"output":3.106,"cache_read":0.145},"limit":{"context":1048576,"output":384000}},"qwen3-coder-30b-a3b-instruct":{"id":"qwen3-coder-30b-a3b-instruct","name":"Qwen3 Coder 30B A3B Instruct","family":"qwen","attachment":false,"reasoning":true,"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.053,"output":0.222},"limit":{"context":262000,"output":262000}}}},"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}},"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/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-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-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/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-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-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/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-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-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-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/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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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/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/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-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-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-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/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}},"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}},"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-5V-Turbo":{"id":"zai-org/GLM-5V-Turbo","name":"zai-org/GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_write":0},"limit":{"context":200000,"output":131072}},"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-5.1":{"id":"zai-org/GLM-5.1","name":"zai-org/GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4,"cache_write":0},"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}},"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}},"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.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.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}},"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}},"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}},"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}},"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}},"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-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":{"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-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":{"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-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-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.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}},"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}},"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}},"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}},"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-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}},"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}},"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.6":{"id":"moonshotai/Kimi-K2.6","name":"moonshotai/Kimi-K2.6","family":"kimi","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262000,"output":262000}},"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,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"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.25},"limit":{"context":262000,"output":262000}},"MiniMaxAI/MiniMax-M2.5":{"id":"MiniMaxAI/MiniMax-M2.5","name":"MiniMaxAI/MiniMax-M2.5","family":"minimax","attachment":false,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2026-02-15","last_updated":"2026-02-15","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2},"limit":{"context":197000,"output":131000}},"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}},"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}}}},"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":{"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.6-27b":{"id":"alibaba/qwen3.6-27b","name":"Qwen 3.6 27B","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":3.5999999999999996},"limit":{"context":256000,"output":256000}},"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/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/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.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.6-plus":{"id":"alibaba/qwen3.6-plus","name":"Qwen 3.6 Plus","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":3,"cache_read":0.09999999999999999,"cache_write":0.625},"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/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-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/qwen-3.6-max-preview":{"id":"alibaba/qwen-3.6-max-preview","name":"Qwen 3.6 Max Preview","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":1.3,"output":7.8,"cache_read":0.26,"cache_write":1.625},"limit":{"context":240000,"output":64000}},"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-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-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-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-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-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-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-235b-a22b-instruct":{"id":"alibaba/qwen3-vl-235b-a22b-instruct","name":"Qwen3 VL 235B A22B Instruct","family":"qwen","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2025-09-24","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.39999999999999997,"output":1.5999999999999999},"limit":{"context":131072,"output":129024}},"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/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.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/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-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-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}},"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}},"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-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}},"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-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-v4-flash":{"id":"deepseek/deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"deepseek/deepseek-v4-pro":{"id":"deepseek/deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-23","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}},"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":{"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-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-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}},"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}},"arcee-ai/trinity-large-thinking":{"id":"arcee-ai/trinity-large-thinking","name":"Trinity Large Thinking","family":"trinity","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":0.8999999999999999},"limit":{"context":262100,"output":80000}},"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}},"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}},"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}},"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-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-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-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-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-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-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}},"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-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-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}},"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}},"zai/glm-5v-turbo":{"id":"zai/glm-5v-turbo","name":"GLM 5V Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":200000,"output":128000}},"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-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-5.1":{"id":"zai/glm-5.1","name":"GLM 5.1","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-16","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.4,"output":4.4,"cache_read":0.26},"limit":{"context":202752,"output":202752}},"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.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.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-5-turbo":{"id":"zai/glm-5-turbo","name":"GLM 5 Turbo","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-15","last_updated":"2026-03-17","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.2,"output":4,"cache_read":0.24},"limit":{"context":202800,"output":131100}},"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.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.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}},"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}},"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}},"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}},"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}},"xai/grok-4.3":{"id":"xai/grok-4.3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-30","last_updated":"2026-05-01","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.19999999999999998},"limit":{"context":1000000,"output":1000000}},"xai/grok-4.20-non-reasoning":{"id":"xai/grok-4.20-non-reasoning","name":"Grok 4.20 Non-Reasoning","family":"grok","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-09","last_updated":"2026-03-23","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-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-4.20-reasoning":{"id":"xai/grok-4.20-reasoning","name":"Grok 4.20 Reasoning","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-09","last_updated":"2026-03-23","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.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-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-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.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-4.20-multi-agent":{"id":"xai/grok-4.20-multi-agent","name":"Grok 4.20 Multi-Agent","family":"grok","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-09","last_updated":"2026-03-23","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6,"cache_read":0.19999999999999998},"limit":{"context":2000000,"output":2000000}},"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-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":{"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-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-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}},"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-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-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-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}},"nvidia/nemotron-3-super-120b-a12b":{"id":"nvidia/nemotron-3-super-120b-a12b","name":"NVIDIA Nemotron 3 Super 120B A12B","family":"nemotron","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.65},"limit":{"context":256000,"output":32000}},"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}},"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}},"inception/mercury-edit-2":{"id":"inception/mercury-edit-2","name":"Mercury Edit 2","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2026-03-30","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":0.75,"cache_read":0.025},"limit":{"context":128000,"output":8192}},"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,"release_date":"2025-02-26","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1},"limit":{"context":32000,"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-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-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/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-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/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-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/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-5.5":{"id":"openai/gpt-5.5","name":"GPT 5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5},"limit":{"context":1000000,"input":872000,"output":128000}},"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/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-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.4-mini":{"id":"openai/gpt-5.4-mini","name":"GPT 5.4 Mini","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000}},"openai/gpt-5.4-nano":{"id":"openai/gpt-5.4-nano","name":"GPT 5.4 Nano","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.19999999999999998,"output":1.25,"cache_read":0.02},"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/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.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-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-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/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/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/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/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-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/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-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.5-pro":{"id":"openai/gpt-5.5-pro","name":"GPT 5.5 Pro","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180},"limit":{"context":1000000,"input":872000,"output":128000}},"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-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-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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":1.6,"cache_read":0.1},"limit":{"context":1047576,"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","pdf"],"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":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-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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10,"cache_read":1.25},"limit":{"context":128000,"output":16384}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"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,"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/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}},"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/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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":60,"cache_read":7.5},"limit":{"context":200000,"output":100000}},"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-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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6,"cache_read":0.08},"limit":{"context":128000,"output":16384}},"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-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}},"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-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/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-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-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}},"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/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/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/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/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/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/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-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/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/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/mistral-small":{"id":"mistral/mistral-small","name":"Mistral Small (latest)","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-06","release_date":"2026-03-16","last_updated":"2026-03-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":256000,"output":256000}},"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/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/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/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/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}},"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}},"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.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-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-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-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.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-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-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}},"minimax/minimax-m2.7":{"id":"minimax/minimax-m2.7","name":"Minimax M2.7","family":"minimax","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131000}},"minimax/minimax-m2.7-highspeed":{"id":"minimax/minimax-m2.7-highspeed","name":"MiniMax M2.7 High Speed","family":"minimax","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":2.4,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131100}},"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.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.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":{"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}},"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}},"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}},"kwaipilot/kat-coder-pro-v2":{"id":"kwaipilot/kat-coder-pro-v2","name":"Kat Coder Pro V2","family":"kat-coder","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-30","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":256000,"output":256000}},"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/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/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-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-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/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-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/gemma-4-31b-it":{"id":"google/gemma-4-31b-it","name":"Gemma 4 31B IT","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.14,"output":0.39999999999999997},"limit":{"context":262144,"output":131072}},"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/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/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-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":{"id":"google/gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","family":"gemini","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-05-07","last_updated":"2026-05-08","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.25,"output":1.5,"cache_read":0.03},"limit":{"context":1000000,"output":65000}},"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-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-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-embedding-2":{"id":"google/gemini-embedding-2","name":"Gemini Embedding 2","family":"gemini-embedding","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-10","last_updated":"2026-03-23","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"limit":{"context":0,"output":0}},"google/gemma-4-26b-a4b-it":{"id":"google/gemma-4-26b-a4b-it","name":"Gemma 4 26B A4B IT","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-03","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.13,"output":0.39999999999999997},"limit":{"context":262144,"output":131072}},"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/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-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/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.03,"input_audio":1},"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,"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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"limit":{"context":1048576,"output":65536}},"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}},"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.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-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-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.6":{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262000,"output":262000}},"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":{"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"},"interfaze/interfaze-beta":{"id":"interfaze/interfaze-beta","name":"Interfaze Beta","attachment":false,"reasoning":true,"tool_call":false,"temperature":true,"release_date":"2025-10-07","last_updated":"2026-04-29","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.5,"output":3.5},"limit":{"context":1000000,"output":32000}},"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-31","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-opus-4.7":{"id":"anthropic/claude-opus-4.7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"release_date":"2026-04-16","last_updated":"2026-04-16","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-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-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-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-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":128000}},"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-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":{"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-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.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.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-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-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}},"xiaomi/mimo-v2.5-pro":{"id":"xiaomi/mimo-v2.5-pro","name":"MiMo V2.5 Pro","family":"mimo-v2.5-pro","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-05-01","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.19999999999999998},"limit":{"context":1050000,"output":131000}},"xiaomi/mimo-v2.5":{"id":"xiaomi/mimo-v2.5","name":"MiMo M2.5","family":"mimo-v2.5","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-22","last_updated":"2026-05-01","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.39999999999999997,"output":2,"cache_read":0.08},"limit":{"context":1050000,"output":131100}},"xiaomi/mimo-v2-pro":{"id":"xiaomi/mimo-v2-pro","name":"MiMo V2 Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.19999999999999998},"limit":{"context":1000000,"output":128000}},"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}},"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}},"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}},"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}},"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-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-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-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}}}},"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":{"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":{"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.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131072}},"MiniMax-M2.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"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}}}},"llmgateway":{"id":"llmgateway","env":["LLMGATEWAY_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.llmgateway.io/v1","name":"LLM Gateway","doc":"https://llmgateway.io/docs","models":{"gpt-4o-mini-search-preview":{"id":"gpt-4o-mini-search-preview","name":"GPT-4o Mini Search Preview","family":"gpt","attachment":true,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2024-10-01","last_updated":"2024-10-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6},"limit":{"context":128000,"output":16384}},"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,"structured_output":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}},"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,"structured_output":true,"temperature":true,"release_date":"2025-07-08","last_updated":"2025-07-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":131072,"output":8192}},"llama-4-scout":{"id":"llama-4-scout","name":"Llama 4 Scout","family":"llama","attachment":false,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2025-04-05","last_updated":"2025-04-05","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.18,"output":0.59},"limit":{"context":32768,"output":16384},"status":"beta"},"hermes-2-pro-llama-3-8b":{"id":"hermes-2-pro-llama-3-8b","name":"Hermes 2 Pro Llama 3 8B","family":"hermes","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2024-05-27","last_updated":"2024-05-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.14},"limit":{"context":8192,"output":8192}},"qwen-coder-plus":{"id":"qwen-coder-plus","name":"Qwen Coder Plus","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2024-09-18","last_updated":"2024-09-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":1},"limit":{"context":131072,"output":8192}},"auto":{"id":"auto","name":"Auto Route","family":"auto","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2024-01-01","last_updated":"2024-01-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":128000,"output":16384}},"glm-4.6v-flashx":{"id":"glm-4.6v-flashx","name":"GLM-4.6V FlashX","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-08","last_updated":"2025-12-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.04,"output":0.4,"cache_read":0},"limit":{"context":128000,"output":16000}},"gemma-2-27b-it-together":{"id":"gemma-2-27b-it-together","name":"Gemma 2 27B IT","family":"gemma","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2024-06-27","last_updated":"2024-06-27","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.08,"output":0.08},"limit":{"context":8192,"output":16384}},"codestral-2508":{"id":"codestral-2508","name":"Codestral","family":"mistral","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-07-30","last_updated":"2025-07-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.9},"limit":{"context":256000,"output":16384}},"gemma-3-1b-it":{"id":"gemma-3-1b-it","name":"Gemma 3 1B IT","family":"gemma","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2025-03-12","last_updated":"2025-03-12","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.08,"output":0.3},"limit":{"context":1000000,"output":16384}},"glm-4-32b-0414-128k":{"id":"glm-4-32b-0414-128k","name":"GLM-4 32B (0414-128k)","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-14","last_updated":"2025-04-14","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.1,"output":0.1},"limit":{"context":128000,"output":16384}},"seed-1-6-flash-250715":{"id":"seed-1-6-flash-250715","name":"Seed 1.6 Flash (250715)","family":"seed","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-07-26","last_updated":"2025-07-26","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.07,"output":0.3,"cache_read":0.01},"limit":{"context":256000,"output":8192}},"seed-1-6-250615":{"id":"seed-1-6-250615","name":"Seed 1.6 (250615)","family":"seed","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-06-25","last_updated":"2025-06-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2,"cache_read":0.05},"limit":{"context":256000,"output":8192}},"qwen3-vl-235b-a22b-thinking":{"id":"qwen3-vl-235b-a22b-thinking","name":"Qwen3 VL 235B A22B Thinking","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-09-15","last_updated":"2025-09-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":131072,"output":8192}},"qwen3-vl-30b-a3b-thinking":{"id":"qwen3-vl-30b-a3b-thinking","name":"Qwen3 VL 30B A3B Thinking","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-10-02","last_updated":"2025-10-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"qwen2-5-vl-32b-instruct":{"id":"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-15","last_updated":"2025-03-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":0.3},"limit":{"context":131072,"output":8192}},"qwen3-vl-8b-instruct":{"id":"qwen3-vl-8b-instruct","name":"Qwen3 VL 8B Instruct","family":"qwen","attachment":true,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2025-08-19","last_updated":"2025-08-19","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"claude-3-7-sonnet":{"id":"claude-3-7-sonnet","name":"Claude 3.7 Sonnet","family":"claude","attachment":false,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2025-02-24","last_updated":"2025-02-24","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15,"cache_read":0.3},"limit":{"context":200000,"output":8192}},"gemini-pro-latest":{"id":"gemini-pro-latest","name":"Gemini Pro Latest","family":"gemini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-02-27","last_updated":"2026-02-27","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":12,"cache_read":0.2},"limit":{"context":1048576,"output":65536}},"claude-3-5-haiku":{"id":"claude-3-5-haiku","name":"Claude 3.5 Haiku","family":"claude","attachment":false,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2024-10-22","last_updated":"2024-10-22","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":4,"cache_read":0.08},"limit":{"context":200000,"output":8192},"status":"deprecated"},"qwen-max-latest":{"id":"qwen-max-latest","name":"Qwen Max Latest","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-01-25","last_updated":"2025-01-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.6,"output":6.4},"limit":{"context":32768,"output":8192}},"glm-4.6v-flash":{"id":"glm-4.6v-flash","name":"GLM-4.6V Flash","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-08","last_updated":"2025-12-08","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":128000,"output":16000},"status":"beta"},"qwen3-30b-a3b-instruct-2507":{"id":"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-08","last_updated":"2025-07-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"minimax-text-01":{"id":"minimax-text-01","name":"MiniMax Text 01","family":"minimax","attachment":false,"reasoning":true,"tool_call":false,"structured_output":false,"temperature":true,"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":1000000,"output":131072}},"qwen3-32b-fp8":{"id":"qwen3-32b-fp8","name":"Qwen3 32B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"llama-4-scout-17b-instruct":{"id":"llama-4-scout-17b-instruct","name":"Llama 4 Scout 17B Instruct","family":"llama","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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.17,"output":0.66},"limit":{"context":8192,"output":2048}},"qwen3-4b-fp8":{"id":"qwen3-4b-fp8","name":"Qwen3 4B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.03,"output":0.05},"limit":{"context":131072,"output":8192}},"ministral-8b-2512":{"id":"ministral-8b-2512","name":"Ministral 8B","family":"mistral","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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.15,"output":0.15},"limit":{"context":262144,"output":8192}},"gemma-3-27b":{"id":"gemma-3-27b","name":"Gemma 3 27B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2025-03-12","last_updated":"2025-03-12","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.27,"output":0.27},"limit":{"context":128000,"output":16384}},"qwen3-vl-flash":{"id":"qwen3-vl-flash","name":"Qwen3 VL Flash","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-10-09","last_updated":"2025-10-09","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.05,"output":0.4,"cache_read":0.01},"limit":{"context":1000000,"output":32000}},"llama-3.1-70b-instruct":{"id":"llama-3.1-70b-instruct","name":"Llama 3.1 70B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"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":2048},"status":"beta"},"seed-1-8-251228":{"id":"seed-1-8-251228","name":"Seed 1.8 (251228)","family":"seed","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-18","last_updated":"2025-12-18","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2,"cache_read":0.05},"limit":{"context":256000,"output":8192}},"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,"structured_output":true,"temperature":true,"release_date":"2025-07-08","last_updated":"2025-07-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":131072,"output":8192}},"seed-1-6-250915":{"id":"seed-1-6-250915","name":"Seed 1.6 (250915)","family":"seed","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-09-15","last_updated":"2025-09-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.25,"output":2,"cache_read":0.05},"limit":{"context":256000,"output":8192}},"glm-4.5-x":{"id":"glm-4.5-x","name":"GLM-4.5 X","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-07-28","last_updated":"2025-07-28","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":2.2,"output":8.9,"cache_read":0.45},"limit":{"context":128000,"output":16384},"status":"beta"},"qwen3-30b-a3b-thinking-2507":{"id":"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-08","last_updated":"2025-07-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"grok-4-fast-reasoning":{"id":"grok-4-fast-reasoning","name":"Grok 4 Fast Reasoning","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"structured_output":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,"cache_read":0.05},"limit":{"context":2000000,"output":30000}},"deepseek-v3.1":{"id":"deepseek-v3.1","name":"DeepSeek V3.1","family":"deepseek","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-08-21","last_updated":"2025-08-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.56,"output":1.68,"cache_read":0.11},"limit":{"context":128000,"output":32768}},"ministral-3b-2512":{"id":"ministral-3b-2512","name":"Ministral 3B","family":"mistral","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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":131072,"output":8192}},"qwen-plus-latest":{"id":"qwen-plus-latest","name":"Qwen Plus Latest","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-01-25","last_updated":"2025-01-25","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.3,"output":0.9},"limit":{"context":131072,"output":8192}},"llama-3.1-nemotron-ultra-253b":{"id":"llama-3.1-nemotron-ultra-253b","name":"Llama 3.1 Nemotron Ultra 253B","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-04-07","last_updated":"2025-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":1.8},"limit":{"context":128000,"output":8192}},"llama-4-maverick-17b-instruct":{"id":"llama-4-maverick-17b-instruct","name":"Llama 4 Maverick 17B Instruct","family":"llama","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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.24,"output":0.97},"limit":{"context":8192,"output":2048}},"grok-4-0709":{"id":"grok-4-0709","name":"Grok 4 (0709)","family":"grok","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-07-09","last_updated":"2025-07-09","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15},"limit":{"context":256000,"output":256000}},"qwen3-30b-a3b-fp8":{"id":"qwen3-30b-a3b-fp8","name":"Qwen3 30B A3B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"minimax-m2.1-lightning":{"id":"minimax-m2.1-lightning","name":"MiniMax M2.1 Lightning","family":"minimax","attachment":false,"reasoning":true,"tool_call":false,"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.12,"output":0.48},"limit":{"context":196608,"output":131072}},"qwen3-max-2026-01-23":{"id":"qwen3-max-2026-01-23","name":"Qwen3 Max (2026-01-23)","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-01-23","last_updated":"2026-01-23","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":3,"output":15,"cache_read":0.6},"limit":{"context":256000,"output":32800}},"llama-3.2-3b-instruct":{"id":"llama-3.2-3b-instruct","name":"Llama 3.2 3B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-09-18","last_updated":"2024-09-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.03,"output":0.05},"limit":{"context":32768,"output":32000}},"qwen3-coder-next":{"id":"qwen3-coder-next","name":"Qwen3 Coder Next","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-10-15","last_updated":"2025-10-15","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":4},"limit":{"context":262144,"output":65536}},"gpt-4o-search-preview":{"id":"gpt-4o-search-preview","name":"GPT-4o Search Preview","family":"gpt","attachment":true,"reasoning":false,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2024-10-01","last_updated":"2024-10-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10},"limit":{"context":128000,"output":16384}},"custom":{"id":"custom","name":"Custom Model","family":"auto","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2024-01-01","last_updated":"2024-01-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":128000,"output":16384}},"qwen3-vl-30b-a3b-instruct":{"id":"qwen3-vl-30b-a3b-instruct","name":"Qwen3 VL 30B A3B Instruct","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-10-02","last_updated":"2025-10-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":131072,"output":8192}},"deepseek-v3.2":{"id":"deepseek-v3.2","name":"DeepSeek V3.2","family":"deepseek","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-09-29","last_updated":"2025-09-29","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.28,"output":0.42,"cache_read":0.03},"limit":{"context":163840,"output":16384}},"qwen3-235b-a22b-fp8":{"id":"qwen3-235b-a22b-fp8","name":"Qwen3 235B A22B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":2.5},"limit":{"context":131072,"output":8192}},"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":"2025-08-05","last_updated":"2025-08-05","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.1,"output":0.5},"limit":{"context":131072,"output":32766}},"kimi-k2":{"id":"kimi-k2","name":"Kimi K2","family":"kimi","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":1,"output":3,"cache_read":0.5},"limit":{"context":131072,"output":16384}},"llama-3-8b-instruct":{"id":"llama-3-8b-instruct","name":"Llama 3 8B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2025-04-03","last_updated":"2025-04-03","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.04,"output":0.04},"limit":{"context":8192,"output":8192}},"qwen3-vl-235b-a22b-instruct":{"id":"qwen3-vl-235b-a22b-instruct","name":"Qwen3 VL 235B A22B Instruct","family":"qwen","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-09-15","last_updated":"2025-09-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":131072,"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,"release_date":"2025-08-05","last_updated":"2025-08-05","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.75},"limit":{"context":131072,"output":32766}},"qwen25-coder-7b":{"id":"qwen25-coder-7b","name":"Qwen2.5 Coder 7B","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-09-19","last_updated":"2024-09-19","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.05,"output":0.05},"limit":{"context":131072,"output":8192}},"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,"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.22,"output":0.22},"limit":{"context":128000,"output":2048},"status":"beta"},"llama-3-70b-instruct":{"id":"llama-3-70b-instruct","name":"Llama 3 70B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-04-18","last_updated":"2024-04-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.51,"output":0.74},"limit":{"context":8192,"output":8000}},"deepseek-r1-0528":{"id":"deepseek-r1-0528","name":"DeepSeek R1 (0528)","family":"deepseek","attachment":false,"reasoning":true,"tool_call":false,"structured_output":false,"temperature":true,"release_date":"2025-05-28","last_updated":"2025-05-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.8,"output":2.4},"limit":{"context":64000,"output":16384},"status":"beta"},"glm-4.5-airx":{"id":"glm-4.5-airx","name":"GLM-4.5 AirX","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-07-28","last_updated":"2025-07-28","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.1,"output":4.5,"cache_read":0.22},"limit":{"context":128000,"output":16384}},"ministral-14b-2512":{"id":"ministral-14b-2512","name":"Ministral 14B","family":"mistral","attachment":true,"reasoning":false,"tool_call":false,"structured_output":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.2,"output":0.2},"limit":{"context":262144,"output":8192}},"llama-3.2-11b-instruct":{"id":"llama-3.2-11b-instruct","name":"Llama 3.2 11B Instruct","family":"llama","attachment":false,"reasoning":false,"tool_call":false,"structured_output":true,"temperature":true,"release_date":"2024-09-25","last_updated":"2024-09-25","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.07,"output":0.33},"limit":{"context":128000,"output":8192}},"claude-3-opus":{"id":"claude-3-opus","name":"Claude 3 Opus","family":"claude","attachment":true,"reasoning":false,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2024-03-04","last_updated":"2024-03-04","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":75,"cache_read":1.5},"limit":{"context":200000,"output":4096}},"minimax-m2.7":{"id":"minimax-m2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131072}},"grok-4-20-beta-0309-non-reasoning":{"id":"grok-4-20-beta-0309-non-reasoning","name":"Grok 4.20 (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}},"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}},"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-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}},"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}},"kimi-k2.5":{"id":"kimi-k2.5","name":"Kimi K2.5","family":"kimi-k2.5","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}},"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}},"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}},"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}},"minimax-m2.7-highspeed":{"id":"minimax-m2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"gemma-3n-e4b-it":{"id":"gemma-3n-e4b-it","name":"Gemma 3n 4B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-31","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}},"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}},"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}},"qwen35-397b-a17b":{"id":"qwen35-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-15","last_updated":"2026-02-15","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3.6},"limit":{"context":262144,"output":65536}},"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}},"gpt-5.3-chat-latest":{"id":"gpt-5.3-chat-latest","name":"GPT-5.3 Chat (latest)","family":"gpt","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"temperature":true,"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}},"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-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}},"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}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"output":128000},"experimental":{"modes":{"fast":{"cost":{"input":12.5,"output":75,"cache_read":1.25},"provider":{"body":{"service_tier":"priority"}}}}}},"qwen3.6-35b-a3b":{"id":"qwen3.6-35b-a3b","name":"Qwen3.6 35B-A3B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-17","last_updated":"2026-04-17","modalities":{"input":["text","image","video","audio"],"output":["text"]},"open_weights":true,"cost":{"input":0.248,"output":1.485},"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}},"claude-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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}},"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-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}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":131072}},"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}},"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}},"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}},"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}},"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}},"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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"limit":{"context":1048576,"output":65536}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"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,"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}},"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}},"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}},"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}},"grok-4-20-beta-0309-reasoning":{"id":"grok-4-20-beta-0309-reasoning","name":"Grok 4.20 (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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.15,"output":0.6,"cache_read":0.08},"limit":{"context":128000,"output":16384}},"qwen3.6-plus":{"id":"qwen3.6-plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":false,"cost":{"input":0.276,"output":1.651,"cache_read":0.028,"cache_write":0.344},"limit":{"context":1000000,"output":65536}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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}},"glm-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":24,"cache_read":1.3,"cache_write":0},"limit":{"context":200000,"output":131072}},"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-5.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"limit":{"context":400000,"input":272000,"output":128000}},"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}},"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-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}},"gemma-3-12b-it":{"id":"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,"output":0},"limit":{"context":32768,"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":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}},"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.03,"input_audio":1},"limit":{"context":1048576,"output":65536}},"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}},"gemma-3n-e2b-it":{"id":"gemma-3n-e2b-it","name":"Gemma 3n 2B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","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}},"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}},"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}},"gemini-3.1-flash-lite":{"id":"gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","family":"gemini-flash-lite","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-05-07","last_updated":"2026-05-07","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}},"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}},"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}},"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}},"gemma-3-4b-it":{"id":"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,"output":0},"limit":{"context":32768,"output":8192}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":15,"output":60,"cache_read":7.5},"limit":{"context":200000,"output":100000}},"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}},"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":1050000,"input":922000,"output":128000}},"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}},"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}},"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}},"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}},"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}},"grok-4-3":{"id":"grok-4-3","name":"Grok 4.3","family":"grok","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-05-01","last_updated":"2026-05-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":1.25,"output":2.5,"cache_read":0.2,"context_over_200k":{"input":2.5,"output":5,"cache_read":0.4}},"limit":{"context":1000000,"output":30000}},"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}},"deepseek-v4-flash":{"id":"deepseek-v4-flash","name":"DeepSeek V4 Flash","family":"deepseek-flash","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.28,"cache_read":0.028},"limit":{"context":1000000,"output":384000}},"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}},"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}},"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}},"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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":262144,"output":262144}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","family":"deepseek-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-05","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.74,"output":3.48,"cache_read":0.145},"limit":{"context":1000000,"output":384000}},"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}},"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}},"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}},"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}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":200000,"output":100000}},"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":{"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2.5,"output":10,"cache_read":1.25},"limit":{"context":128000,"output":16384}},"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}},"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}},"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}},"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-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}},"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}},"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":262144,"output":65536}},"qwen3.6-max-preview":{"id":"qwen3.6-max-preview","name":"Qwen3.6 Max Preview","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-04","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1.3,"output":7.8,"cache_read":0.13,"cache_write":1.625},"limit":{"context":262144,"output":65536}},"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}},"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}},"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}},"gpt-5.5-pro":{"id":"gpt-5.5-pro","name":"GPT-5.5 Pro","family":"gpt-pro","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-23","last_updated":"2026-04-23","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":30,"output":180,"context_over_200k":{"input":60,"output":270}},"limit":{"context":1050000,"input":922000,"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":8,"cache_read":0.5},"limit":{"context":1047576,"output":32768}},"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}},"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}},"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}},"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","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":1.6,"cache_read":0.1},"limit":{"context":1047576,"output":32768}},"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}},"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-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}},"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}}}},"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-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}},"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-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-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-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}},"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.2-maas":{"id":"deepseek-ai/deepseek-v3.2-maas","name":"DeepSeek V3.2","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2025-12-17","last_updated":"2026-04-04","modalities":{"input":["text","pdf"],"output":["text"]},"open_weights":true,"cost":{"input":0.56,"output":1.68,"cache_read":0.056},"limit":{"context":163840,"output":65536},"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"}},"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}},"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}},"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"}},"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"}},"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"}},"moonshotai/kimi-k2-thinking-maas":{"id":"moonshotai/kimi-k2-thinking-maas","name":"Kimi K2 Thinking","family":"kimi-thinking","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-08","release_date":"2025-11-13","last_updated":"2025-11-13","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":2.5},"limit":{"context":262144,"output":262144},"provider":{"npm":"@ai-sdk/openai-compatible","api":"https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi"}},"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-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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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-31","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,"context_over_200k":{"input":6,"output":22.5,"cache_read":0.6,"cache_write":7.5}},"limit":{"context":200000,"output":64000},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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-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-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-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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"gemini-3.1-flash-lite":{"id":"gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","family":"gemini-flash-lite","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-05-07","last_updated":"2026-05-07","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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"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-31","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,"context_over_200k":{"input":10,"output":37.5,"cache_read":1,"cache_write":12.5}},"limit":{"context":1000000,"output":128000},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}},"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}},"claude-opus-4-7@default":{"id":"claude-opus-4-7@default","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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},"provider":{"npm":"@ai-sdk/google-vertex/anthropic"}}}},"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/nvidia/nemotron-3-120b-a12b":{"id":"@cf/nvidia/nemotron-3-120b-a12b","name":"Nemotron 3 Super 120B","family":"nemotron","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-11","last_updated":"2026-03-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":1.5},"limit":{"context":256000,"output":256000}},"@cf/openai/gpt-oss-20b":{"id":"@cf/openai/gpt-oss-20b","name":"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.2,"output":0.3},"limit":{"context":128000,"output":16384}},"@cf/openai/gpt-oss-120b":{"id":"@cf/openai/gpt-oss-120b","name":"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.35,"output":0.75},"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":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-04-16","last_updated":"2025-04-16","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.27,"output":0.85},"limit":{"context":128000,"output":16384}},"@cf/google/gemma-4-26b-a4b-it":{"id":"@cf/google/gemma-4-26b-a4b-it","name":"Gemma 4 26B A4B IT","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-12-15","last_updated":"2025-12-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.3},"limit":{"context":256000,"output":16384}},"@cf/moonshotai/kimi-k2.5":{"id":"@cf/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"],"output":["text"]},"open_weights":true,"cost":{"input":0.6,"output":3,"cache_read":0.1},"limit":{"context":256000,"output":256000}},"@cf/moonshotai/kimi-k2.6":{"id":"@cf/moonshotai/kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"limit":{"context":256000,"output":256000}}}},"groq":{"id":"groq","env":["GROQ_API_KEY"],"npm":"@ai-sdk/groq","name":"Groq","doc":"https://console.groq.com/docs/models","models":{"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"},"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"},"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"},"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"},"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}},"allam-2-7b":{"id":"allam-2-7b","name":"ALLaM-2-7b","family":"allam","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-09","release_date":"2024-09","last_updated":"2024-09","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":4096,"output":4096}},"whisper-large-v3":{"id":"whisper-large-v3","name":"Whisper Large V3","family":"whisper","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"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":448,"output":448}},"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}},"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"},"whisper-large-v3-turbo":{"id":"whisper-large-v3-turbo","name":"Whisper Large v3 Turbo","family":"whisper","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","release_date":"2024-10-01","last_updated":"2024-10-01","modalities":{"input":["audio"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":448,"output":448}},"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"},"canopylabs/orpheus-arabic-saudi":{"id":"canopylabs/orpheus-arabic-saudi","name":"Orpheus Arabic Saudi","family":"canopylabs","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2025-12-16","release_date":"2025-12-16","last_updated":"2025-12-16","modalities":{"input":["text"],"output":["audio"]},"open_weights":false,"cost":{"input":40,"output":0},"limit":{"context":4000,"output":50000}},"canopylabs/orpheus-v1-english":{"id":"canopylabs/orpheus-v1-english","name":"Orpheus V1 English","family":"canopylabs","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2025-12-19","release_date":"2025-12-19","last_updated":"2025-12-19","modalities":{"input":["text"],"output":["audio"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":4000,"output":50000}},"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-prompt-guard-2-22m":{"id":"meta-llama/llama-prompt-guard-2-22m","name":"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":true,"cost":{"input":0.03,"output":0.03},"limit":{"context":512,"output":512}},"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},"status":"deprecated"},"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},"status":"deprecated"},"meta-llama/llama-prompt-guard-2-86m":{"id":"meta-llama/llama-prompt-guard-2-86m","name":"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":true,"cost":{"input":0.04,"output":0.04},"limit":{"context":512,"output":512}},"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}},"openai/gpt-oss-safeguard-20b":{"id":"openai/gpt-oss-safeguard-20b","name":"Safety GPT OSS 20B","family":"gpt-oss","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.075,"output":0.3,"cache_read":0.037},"limit":{"context":131072,"output":65536}},"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}},"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":40960}},"groq/compound":{"id":"groq/compound","name":"Compound","family":"groq","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-09-04","release_date":"2025-09-04","last_updated":"2025-09-04","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":8192}},"groq/compound-mini":{"id":"groq/compound-mini","name":"Compound Mini","family":"groq","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-09-04","release_date":"2025-09-04","last_updated":"2025-09-04","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":131072,"output":8192}},"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}}}},"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":{"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-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}},"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-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.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-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}},"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-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}},"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}},"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"}},"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}},"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"}},"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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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-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}},"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}},"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}},"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}},"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"},"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-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}},"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-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"}},"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}},"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.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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"grok-4-20-non-reasoning":{"id":"grok-4-20-non-reasoning","name":"Grok 4.20 (Non-Reasoning)","family":"grok","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"knowledge":"2025-09","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":262000,"output":8192},"status":"beta"},"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}},"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}},"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}},"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}},"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-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}},"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-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}},"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-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}},"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-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}},"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}},"kimi-k2.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi","attachment":false,"reasoning":true,"tool_call":true,"interleaved":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4},"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-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}},"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}},"grok-4-20-reasoning":{"id":"grok-4-20-reasoning","name":"Grok 4.20 (Reasoning)","family":"grok","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-09","release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":2,"output":6},"limit":{"context":262000,"output":8192},"status":"beta"},"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}},"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,"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"}},"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}},"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}},"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}},"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}},"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"}},"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-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}},"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-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}},"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}},"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}},"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}},"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.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}},"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}},"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}},"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}},"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}},"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,"knowledge":"2025-08-31","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},"provider":{"npm":"@ai-sdk/anthropic","api":"https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1"}},"gpt-5.5":{"id":"gpt-5.5","name":"GPT-5.5","family":"gpt","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-12-01","release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":30,"cache_read":0.5,"context_over_200k":{"input":10,"output":45,"cache_read":1}},"limit":{"context":1050000,"input":922000,"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-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-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}},"gpt-5.4-mini":{"id":"gpt-5.4-mini","name":"GPT-5.4 Mini","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":4.5,"cache_read":0.075},"limit":{"context":400000,"input":272000,"output":128000}},"cohere-command-r-08-2024":{"id":"cohere-command-r-08-2024","name":"Command R","family":"command-r","attachment":false,"reasoning":false,"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}},"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.4-nano":{"id":"gpt-5.4-nano","name":"GPT-5.4 Nano","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.2,"output":1.25,"cache_read":0.02},"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":"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}},"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}},"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":{"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.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}},"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}},"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}},"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}},"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-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}},"cohere-command-r-plus-08-2024":{"id":"cohere-command-r-plus-08-2024","name":"Command R+","family":"command-r","attachment":false,"reasoning":false,"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-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}},"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}},"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-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}}}},"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":{"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}},"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}},"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-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}},"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":{"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-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}},"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"},"temperature":true,"release_date":"2026-02-11","last_updated":"2026-02-11","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":3.15},"limit":{"context":204800,"output":131072}},"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}},"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}},"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}},"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}},"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}},"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}}}},"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":{"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}},"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}},"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}},"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}},"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}}}},"tencent-coding-plan":{"id":"tencent-coding-plan","env":["TENCENT_CODING_PLAN_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.lkeap.cloud.tencent.com/coding/v3","name":"Tencent Coding Plan (China)","doc":"https://cloud.tencent.com/document/product/1772/128947","models":{"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}},"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}},"hunyuan-turbos":{"id":"hunyuan-turbos","name":"Hunyuan-TurboS","family":"hunyuan","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":16384}},"hunyuan-t1":{"id":"hunyuan-t1","name":"Hunyuan-T1","family":"hunyuan","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":16384}},"hunyuan-2.0-instruct":{"id":"hunyuan-2.0-instruct","name":"Tencent HY 2.0 Instruct","family":"hunyuan","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"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":204800,"output":32768}},"tc-code-latest":{"id":"tc-code-latest","name":"Auto","family":"auto","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":16384}},"hunyuan-2.0-thinking":{"id":"hunyuan-2.0-thinking","name":"Tencent HY 2.0 Think","family":"hunyuan","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-03-08","last_updated":"2026-03-08","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":131072,"output":16384}}}},"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}},"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}},"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}},"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/models","models":{"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-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-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-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-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-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-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}},"gemma-3n-e4b-it":{"id":"gemma-3n-e4b-it","name":"Gemma 3n 4B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","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}},"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-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-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-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-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-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.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-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-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.125,"context_over_200k":{"input":2.5,"output":15,"cache_read":0.25}},"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}},"gemma-4-31b-it":{"id":"gemma-4-31b-it","name":"Gemma 4 31B","family":"gemma","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":256000,"output":8192}},"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-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-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}},"gemma-3-12b-it":{"id":"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,"output":0},"limit":{"context":32768,"output":8192}},"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.03,"input_audio":1},"limit":{"context":1048576,"output":65536}},"gemma-3n-e2b-it":{"id":"gemma-3n-e2b-it","name":"Gemma 3n 2B","family":"gemma","attachment":true,"reasoning":false,"tool_call":false,"temperature":true,"knowledge":"2024-10","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}},"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":{"id":"gemini-3.1-flash-lite","name":"Gemini 3.1 Flash Lite","family":"gemini-flash-lite","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-05-07","last_updated":"2026-05-07","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}},"gemma-3-4b-it":{"id":"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,"output":0},"limit":{"context":32768,"output":8192}},"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-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-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}},"gemma-3-27b-it":{"id":"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,"output":0},"limit":{"context":131072,"output":8192}},"gemma-4-26b-a4b-it":{"id":"gemma-4-26b-a4b-it","name":"Gemma 4 26B","family":"gemma","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":256000,"output":8192}},"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-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-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-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}}}},"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-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}},"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}}}},"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-k2.5","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-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.6":{"id":"kimi-k2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4,"cache_read":0.16},"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-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":{"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}}}},"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.77,"output":2.75},"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.33,"output":0.33},"limit":{"context":32000,"output":8192}},"mistralai/Mistral-Medium-3.5-128B":{"id":"mistralai/Mistral-Medium-3.5-128B","name":"Mistral Medium 3.5 128B","family":"mistral-medium","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2026-04","release_date":"2026-04-29","last_updated":"2026-04-29","modalities":{"input":["image","text"],"output":["text"]},"open_weights":true,"cost":{"input":1.65,"output":5.5},"limit":{"context":262144,"output":131072}},"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.99,"output":0.99},"limit":{"context":128000,"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.44,"output":0.99},"limit":{"context":128000,"output":8192}},"google/gemma-4-31B-it":{"id":"google/gemma-4-31B-it","name":"Gemma 4 31B Instruct","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-12","release_date":"2026-04-02","last_updated":"2026-04-02","modalities":{"input":["audio","image","text","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.275,"output":0.55},"limit":{"context":128000,"output":8192}}}},"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":{"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}},"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-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}},"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-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-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.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-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-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-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}},"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-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-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/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-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/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}},"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":false,"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-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":{"id":"cohere/cohere-command-r-plus","name":"Cohere Command R+","family":"command-r","attachment":false,"reasoning":false,"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}},"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-plus-08-2024":{"id":"cohere/cohere-command-r-plus-08-2024","name":"Cohere Command R+ 08-2024","family":"command-r","attachment":false,"reasoning":false,"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}},"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}},"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}},"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-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/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/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/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-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/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/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-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}},"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/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}},"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-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-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/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/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.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.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.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-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-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}},"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/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/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-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/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/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}}}},"neuralwatt":{"id":"neuralwatt","env":["NEURALWATT_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.neuralwatt.com/v1","name":"Neuralwatt","doc":"https://portal.neuralwatt.com/docs","models":{"glm-5-fast":{"id":"glm-5-fast","name":"GLM 5 Fast","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.1,"output":3.6},"limit":{"context":200000,"output":200000}},"kimi-k2.6-fast":{"id":"kimi-k2.6-fast","name":"Kimi K2.6 Fast","family":"kimi","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.69,"output":3.22},"limit":{"context":262144,"output":262144}},"qwen3.5-397b-fast":{"id":"qwen3.5-397b-fast","name":"Qwen3.5 397B Fast","family":"qwen","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.69,"output":4.14},"limit":{"context":262144,"output":262144}},"glm-5.1-fast":{"id":"glm-5.1-fast","name":"GLM 5.1 Fast","family":"glm","attachment":false,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.1,"output":3.6},"limit":{"context":200000,"output":200000}},"qwen3.6-35b-fast":{"id":"qwen3.6-35b-fast","name":"Qwen3.6 35B Fast","family":"qwen3.6","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.05,"output":0.1},"limit":{"context":131072,"output":131072}},"kimi-k2.5-fast":{"id":"kimi-k2.5-fast","name":"Kimi K2.5 Fast","family":"kimi","attachment":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.52,"output":2.59},"limit":{"context":262144,"output":262144}},"Qwen/Qwen3.5-397B-A17B-FP8":{"id":"Qwen/Qwen3.5-397B-A17B-FP8","name":"Qwen3.5 397B A17B FP8","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.69,"output":4.14},"limit":{"context":262144,"output":262144}},"Qwen/Qwen3.6-35B-A3B":{"id":"Qwen/Qwen3.6-35B-A3B","name":"Qwen3.6 35B A3B","family":"qwen3.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.05,"output":0.1},"limit":{"context":131072,"output":131072}},"zai-org/GLM-5.1-FP8":{"id":"zai-org/GLM-5.1-FP8","name":"GLM 5.1 FP8","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.1,"output":3.6},"limit":{"context":200000,"output":200000}},"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":true,"reasoning":false,"tool_call":true,"temperature":true,"release_date":"2025-12-09","last_updated":"2025-12-09","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.12,"output":0.35},"limit":{"context":262144,"output":262144}},"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.16},"limit":{"context":16384,"output":16384}},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.69,"output":3.22},"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,"release_date":"2026-01-27","last_updated":"2026-01-27","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.52,"output":2.59},"limit":{"context":262144,"output":262144}},"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.35,"output":1.38},"limit":{"context":196608,"output":196608}}}},"togetherai":{"id":"togetherai","env":["TOGETHER_API_KEY"],"npm":"@ai-sdk/togetherai","name":"Together AI","doc":"https://docs.together.ai/docs/serverless-models","models":{"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}},"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.6-Plus":{"id":"Qwen/Qwen3.6-Plus","name":"Qwen3.6 Plus","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-30","last_updated":"2026-04-30","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.5,"output":3},"limit":{"context":1000000,"output":500000}},"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-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-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}},"zai-org/GLM-5.1":{"id":"zai-org/GLM-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-11","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1.4,"output":4.4},"limit":{"context":202752,"output":131072}},"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}},"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}},"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-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-V4-Pro":{"id":"deepseek-ai/DeepSeek-V4-Pro","name":"DeepSeek V4 Pro","family":"deepseek","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-24","last_updated":"2026-04-24","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":2.1,"output":4.4,"cache_read":0.2},"limit":{"context":512000,"output":384000}},"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}},"google/gemma-4-31B-it":{"id":"google/gemma-4-31B-it","name":"Gemma 4 31B Instruct","family":"gemma","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-07","last_updated":"2026-04-07","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.2,"output":0.5},"limit":{"context":262144,"output":131072}},"moonshotai/Kimi-K2.6":{"id":"moonshotai/Kimi-K2.6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":1.2,"output":4.5,"cache_read":0.2},"limit":{"context":262144,"output":131000}},"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}},"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}},"MiniMaxAI/MiniMax-M2.7":{"id":"MiniMaxAI/MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06},"limit":{"context":202752,"output":131072}}}},"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}},"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}},"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}},"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}},"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}},"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}},"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}}}},"tencent-tokenhub":{"id":"tencent-tokenhub","env":["TENCENT_TOKENHUB_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://tokenhub.tencentmaas.com/v1","name":"Tencent TokenHub","doc":"https://cloud.tencent.com/document/product/1823/130050","models":{"hy3-preview":{"id":"hy3-preview","name":"Hy3 preview","family":"Hy","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-04-20","last_updated":"2026-04-20","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"cache_write":0},"limit":{"context":256000,"output":64000}}}},"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-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-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-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-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-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-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-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-31","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-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-opus-4-7":{"id":"claude-opus-4-7","name":"Claude Opus 4.7","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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-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-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-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-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-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-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-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-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-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-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-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},"experimental":{"modes":{"fast":{"cost":{"input":30,"output":150,"cache_read":3,"cache_write":37.5},"provider":{"body":{"speed":"fast"},"headers":{"anthropic-beta":"fast-mode-2026-02-01"}}}}}},"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-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-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}}}},"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-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-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-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}},"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-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}},"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}},"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}}}},"hpc-ai":{"id":"hpc-ai","env":["HPC_AI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.hpc-ai.com/inference/v1","name":"HPC-AI","doc":"https://www.hpc-ai.com/doc/docs/quickstart/","models":{"zai-org/glm-5.1":{"id":"zai-org/glm-5.1","name":"GLM 5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-04-08","last_updated":"2026-04-08","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.66,"output":2,"cache_read":0.12},"limit":{"context":202000,"output":202000}},"minimax/minimax-m2.5":{"id":"minimax/minimax-m2.5","name":"MiniMax M2.5","family":"minimax-m2.5","attachment":false,"reasoning":true,"tool_call":true,"structured_output":false,"temperature":true,"release_date":"2026-02-12","last_updated":"2026-03-25","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.14,"output":0.56,"cache_read":0.014},"limit":{"context":1000000,"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":{"field":"reasoning_content"},"structured_output":true,"temperature":false,"knowledge":"2025-01-01","release_date":"2026-01-01","last_updated":"2026-03-25","modalities":{"input":["text","image","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.21,"output":1,"cache_read":0.03},"limit":{"context":262144,"output":262144}}}},"gitlab":{"id":"gitlab","env":["GITLAB_TOKEN"],"npm":"gitlab-ai-provider","name":"GitLab Duo","doc":"https://docs.gitlab.com/user/duo_agent_platform/","models":{"duo-chat-gpt-5-4-nano":{"id":"duo-chat-gpt-5-4-nano","name":"Agentic Chat (GPT-5.4 Nano)","family":"gpt-nano","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","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-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-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-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-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-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}},"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-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-gpt-5-4":{"id":"duo-chat-gpt-5-4","name":"Agentic Chat (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":0,"output":0},"limit":{"context":1050000,"input":922000,"output":128000}},"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-3-codex":{"id":"duo-chat-gpt-5-3-codex","name":"Agentic Chat (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":0,"output":0},"limit":{"context":400000,"input":272000,"output":128000}},"duo-chat-gpt-5-4-mini":{"id":"duo-chat-gpt-5-4-mini","name":"Agentic Chat (GPT-5.4 Mini)","family":"gpt-mini","attachment":true,"reasoning":true,"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2025-08-31","release_date":"2026-03-17","last_updated":"2026-03-17","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0},"limit":{"context":400000,"input":272000,"output":128000}},"duo-chat-opus-4-7":{"id":"duo-chat-opus-4-7","name":"Agentic Chat (Claude Opus 4.7)","family":"claude-opus","attachment":true,"reasoning":true,"tool_call":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-04-16","last_updated":"2026-04-16","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-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-05-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}}}},"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.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0.4,"output":2,"cache_read":0.08},"limit":{"context":262144,"output":131072}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":2,"cache_read":0.08,"context_over_200k":{"input":0.8,"output":4,"cache_read":0.16}},"limit":{"context":1048576,"output":131072}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":1,"output":3,"cache_read":0.2,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"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":262144,"output":65536}}}},"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":{"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}},"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}},"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}},"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}},"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}},"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}},"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-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}},"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}},"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}},"moonshotai/chat-completion/models/Kimi-K2_6":{"id":"moonshotai/chat-completion/models/Kimi-K2_6","name":"Kimi K2.6","family":"kimi-k2.6","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2025-01","release_date":"2026-04-21","last_updated":"2026-04-21","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.95,"output":4},"limit":{"context":262144,"output":262144}}}},"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":{"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":{"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.7":{"id":"MiniMax-M2.7","name":"MiniMax-M2.7","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2,"cache_read":0.06,"cache_write":0.375},"limit":{"context":204800,"output":131072}},"MiniMax-M2.7-highspeed":{"id":"MiniMax-M2.7-highspeed","name":"MiniMax-M2.7-highspeed","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-18","last_updated":"2026-03-18","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}},"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}}}},"regolo-ai":{"id":"regolo-ai","env":["REGOLO_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://api.regolo.ai/v1","name":"Regolo AI","doc":"https://docs.regolo.ai/","models":{"mistral-small3.2":{"id":"mistral-small3.2","name":"Mistral Small 3.2","family":"mistral-small","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2025-01-31","last_updated":"2025-01-31","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.5,"output":2.2},"limit":{"context":120000,"output":120000}},"qwen3-embedding-8b":{"id":"qwen3-embedding-8b","name":"Qwen3-Embedding-8B","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.1,"output":0.1},"limit":{"context":32768,"output":8192}},"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,"release_date":"2025-04-28","last_updated":"2025-04-28","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.6,"output":2.7},"limit":{"context":128000,"output":16384}},"qwen3-reranker-4b":{"id":"qwen3-reranker-4b","name":"Qwen3-Reranker-4B","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":false,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.12,"output":0.12},"limit":{"context":32768,"output":8192}},"mistral-small-4-119b":{"id":"mistral-small-4-119b","name":"Mistral Small 4 119B","family":"mistral-small","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-15","last_updated":"2026-03-15","modalities":{"input":["text","image"],"output":["text"]},"open_weights":false,"cost":{"input":0.75,"output":3},"limit":{"context":256000,"output":16384}},"qwen3.5-122b":{"id":"qwen3.5-122b","name":"Qwen3.5-122B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.9,"output":3.6},"limit":{"context":262144,"output":16384}},"qwen-image":{"id":"qwen-image","name":"Qwen-Image","family":"qwen","attachment":false,"reasoning":false,"tool_call":false,"temperature":true,"release_date":"2026-03-01","last_updated":"2026-03-01","modalities":{"input":["text"],"output":["image"]},"open_weights":false,"cost":{"input":0.5,"output":2},"limit":{"context":8192,"output":4096}},"qwen3-coder-next":{"id":"qwen3-coder-next","name":"Qwen3-Coder-Next","family":"qwen","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-01","last_updated":"2026-03-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.3,"output":1.2},"limit":{"context":262144,"output":16384}},"minimax-m2.5":{"id":"minimax-m2.5","name":"MiniMax 2.5","family":"minimax","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-10","last_updated":"2026-03-10","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.8,"output":3.5},"limit":{"context":190000,"output":64000}},"gpt-oss-20b":{"id":"gpt-oss-20b","name":"GPT-OSS-20B","family":"gpt-oss","attachment":false,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-03-01","last_updated":"2026-03-01","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0.4,"output":1.8},"limit":{"context":128000,"output":16384}},"qwen3.5-9b":{"id":"qwen3.5-9b","name":"Qwen3.5-9B","family":"qwen","attachment":true,"reasoning":true,"tool_call":true,"temperature":true,"release_date":"2026-02-01","last_updated":"2026-02-01","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"cost":{"input":0.15,"output":0.6},"limit":{"context":262144,"output":8192}},"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":false,"cost":{"input":1,"output":4.2},"limit":{"context":128000,"output":16384}},"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,"release_date":"2025-04-07","last_updated":"2025-04-07","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0.05,"output":0.25},"limit":{"context":120000,"output":120000}}}},"xiaomi-token-plan-ams":{"id":"xiaomi-token-plan-ams","env":["XIAOMI_API_KEY"],"npm":"@ai-sdk/openai-compatible","api":"https://token-plan-ams.xiaomimimo.com/v1","name":"Xiaomi Token Plan (Europe)","doc":"https://platform.xiaomimimo.com/#/docs","models":{"mimo-v2-tts":{"id":"mimo-v2-tts","name":"MiMo-V2-TTS","family":"mimo","attachment":false,"reasoning":false,"tool_call":false,"release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["audio"]},"open_weights":true,"cost":{"input":0,"output":0},"limit":{"context":8192,"output":16384}},"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,"output":0,"cache_read":0},"limit":{"context":262144,"output":65536}},"mimo-v2-pro":{"id":"mimo-v2-pro","name":"MiMo-V2-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":2,"output":6,"cache_read":0.4}},"limit":{"context":1048576,"output":131072}},"mimo-v2.5":{"id":"mimo-v2.5","name":"MiMo-V2.5","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}},"mimo-v2-omni":{"id":"mimo-v2-omni","name":"MiMo-V2-Omni","family":"mimo","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-03-18","last_updated":"2026-03-18","modalities":{"input":["text","image","audio","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":0,"output":0,"cache_read":0},"limit":{"context":262144,"output":131072}},"mimo-v2.5-pro":{"id":"mimo-v2.5-pro","name":"MiMo-V2.5-Pro","family":"mimo","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"cost":{"input":0,"output":0,"cache_read":0,"context_over_200k":{"input":0,"output":0,"cache_read":0}},"limit":{"context":1048576,"output":131072}}}},"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-5v-turbo":{"id":"glm-5v-turbo","name":"GLM-5V-Turbo","family":"glm","attachment":true,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"temperature":true,"release_date":"2026-04-01","last_updated":"2026-04-01","modalities":{"input":["text","image","video","pdf"],"output":["text"]},"open_weights":false,"cost":{"input":5,"output":22,"cache_read":1.2,"cache_write":0},"limit":{"context":200000,"output":131072}},"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-5.1":{"id":"glm-5.1","name":"GLM-5.1","family":"glm","attachment":false,"reasoning":true,"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"release_date":"2026-03-27","last_updated":"2026-03-27","modalities":{"input":["text"],"output":["text"]},"open_weights":false,"cost":{"input":6,"output":24,"cache_read":1.3,"cache_write":0},"limit":{"context":200000,"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.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.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.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.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.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.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}}}},"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}}}}}
+export const snapshot = {
+  "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": {
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "grok-4.20-multi-agent-beta-0309": {
+        id: "grok-4.20-multi-agent-beta-0309",
+        name: "grok-4.20-multi-agent-beta-0309",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6 },
+        limit: { context: 2000000, output: 30000 },
+      },
+      "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 },
+      },
+      "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-16",
+        last_updated: "2025-10-16",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 5 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "claude-opus-4-5-20251101": {
+        id: "claude-opus-4-5-20251101",
+        name: "claude-opus-4-5-20251101",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-03-31",
+        release_date: "2025-11-25",
+        last_updated: "2025-11-25",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 25 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "glm-5v-turbo": {
+        id: "glm-5v-turbo",
+        name: "GLM-5V-Turbo",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.72, output: 3.2 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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.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.286, output: 1.142 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "claude-3-5-haiku-20241022": {
+        id: "claude-3-5-haiku-20241022",
+        name: "claude-3-5-haiku-20241022",
+        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 },
+        limit: { context: 200000, output: 8192 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-12",
+        last_updated: "2026-02-12",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 2.6 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-31",
+        release_date: "2026-02-18",
+        last_updated: "2026-03-13",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15 },
+        limit: { context: 1000000, output: 64000 },
+      },
+      "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 },
+      },
+      "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-20",
+        last_updated: "2026-01-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.0715, output: 0.429 },
+        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 },
+      },
+      "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 },
+      },
+      "grok-4.20-beta-0309-non-reasoning": {
+        id: "grok-4.20-beta-0309-non-reasoning",
+        name: "grok-4.20-beta-0309-non-reasoning",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6 },
+        limit: { context: 2000000, output: 30000 },
+      },
+      "claude-opus-4-7": {
+        id: "claude-opus-4-7",
+        name: "claude-opus-4-7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-17",
+        last_updated: "2026-04-17",
+        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 },
+      },
+      "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-08",
+        last_updated: "2025-08-08",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 2 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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 },
+      },
+      "MiniMax-M2.7": {
+        id: "MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-19",
+        last_updated: "2026-03-19",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3, output: 1.2 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "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 },
+      },
+      "claude-sonnet-4-5-20250929": {
+        id: "claude-sonnet-4-5-20250929",
+        name: "claude-sonnet-4-5-20250929",
+        family: "claude-sonnet",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-07-31",
+        release_date: "2025-09-30",
+        last_updated: "2025-09-30",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "claude-3-5-haiku-latest": {
+        id: "claude-3-5-haiku-latest",
+        name: "claude-3-5-haiku-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 },
+        limit: { context: 200000, 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 },
+      },
+      "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-12",
+        last_updated: "2025-12-12",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.75, output: 14 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "gpt-5.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "gpt-5.4-mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-19",
+        last_updated: "2026-03-19",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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-5.1": {
+        id: "glm-5.1",
+        name: "glm-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-10",
+        last_updated: "2026-04-10",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.86, output: 3.5 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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 },
+      },
+      "gpt-5.4-nano": {
+        id: "gpt-5.4-nano",
+        name: "gpt-5.4-nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-19",
+        last_updated: "2026-03-19",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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 },
+      },
+      "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-29",
+        last_updated: "2025-07-29",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.286, output: 1.142 },
+        limit: { context: 131072, output: 98304 },
+      },
+      "gpt-5.4-mini-2026-03-17": {
+        id: "gpt-5.4-mini-2026-03-17",
+        name: "gpt-5.4-mini-2026-03-17",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-19",
+        last_updated: "2026-03-19",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5 },
+        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: 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 },
+      },
+      "gpt-5.2-chat-latest": {
+        id: "gpt-5.2-chat-latest",
+        name: "gpt-5.2-chat-latest",
+        family: "gpt-codex",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        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 },
+      },
+      "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 },
+      },
+      "gemini-3.1-flash-image-preview": {
+        id: "gemini-3.1-flash-image-preview",
+        name: "gemini-3.1-flash-image-preview",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-02-27",
+        last_updated: "2026-02-27",
+        modalities: { input: ["text", "image", "pdf"], output: ["text", "image"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 60 },
+        limit: { context: 131072, output: 32768 },
+      },
+      "MiniMax-M2.7-highspeed": {
+        id: "MiniMax-M2.7-highspeed",
+        name: "MiniMax-M2.7-highspeed",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-19",
+        last_updated: "2026-03-19",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.6, output: 4.8 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "glm-4.5-x": {
+        id: "glm-4.5-x",
+        name: "glm-4.5-x",
+        family: "glm",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2025-07-29",
+        last_updated: "2025-07-29",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.143, output: 2.29 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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 },
+      },
+      "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"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.25, output: 10 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-29",
+        last_updated: "2025-07-29",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1143, output: 0.286 },
+        limit: { context: 131072, output: 98304 },
+      },
+      "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, cache_read: 0, cache_write: 0, context_over_200k: { input: 60, output: 270 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+      },
+      "glm-5-turbo": {
+        id: "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-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.72, output: 3.2 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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 },
+      },
+      "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-25",
+        last_updated: "2025-11-25",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 25 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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-12",
+        last_updated: "2025-08-12",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.29, output: 0.86 },
+        limit: { context: 64000, output: 16384 },
+      },
+      "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.286, output: 1.142 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "claude-opus-4-6-thinking": {
+        id: "claude-opus-4-6-thinking",
+        name: "claude-opus-4-6-thinking",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-02-06",
+        last_updated: "2026-03-13",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 25 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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 },
+      },
+      "claude-sonnet-4-6-thinking": {
+        id: "claude-sonnet-4-6-thinking",
+        name: "claude-sonnet-4-6-thinking",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08",
+        release_date: "2026-02-18",
+        last_updated: "2026-03-13",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15 },
+        limit: { context: 1000000, output: 64000 },
+      },
+      "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.145, output: 0.43 },
+        limit: { context: 128000, output: 32768 },
+      },
+      "claude-opus-4-1-20250805": {
+        id: "claude-opus-4-1-20250805",
+        name: "claude-opus-4-1-20250805",
+        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 },
+        limit: { context: 200000, output: 32000 },
+      },
+      "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,
+          cache_write: 0,
+          context_over_200k: { input: 5, output: 22.5 },
+        },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+      },
+      "gpt-5.1-chat-latest": {
+        id: "gpt-5.1-chat-latest",
+        name: "gpt-5.1-chat-latest",
+        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"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.25, output: 10 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "claude-haiku-4-5-20251001": {
+        id: "claude-haiku-4-5-20251001",
+        name: "claude-haiku-4-5-20251001",
+        family: "claude-haiku",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-02-28",
+        release_date: "2025-10-16",
+        last_updated: "2025-10-16",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 5 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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 },
+      },
+      "gpt-5.4-nano-2026-03-17": {
+        id: "gpt-5.4-nano-2026-03-17",
+        name: "gpt-5.4-nano-2026-03-17",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-19",
+        last_updated: "2026-03-19",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "claude-sonnet-4-20250514": {
+        id: "claude-sonnet-4-20250514",
+        name: "claude-sonnet-4-20250514",
+        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 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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 },
+      },
+      "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-06",
+        last_updated: "2026-03-13",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 25 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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 },
+      },
+      "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 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "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 },
+      },
+      "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-08",
+        last_updated: "2025-10-08",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 15, output: 120 },
+        limit: { context: 400000, input: 272000, output: 272000 },
+      },
+      "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-05-13",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2.5, output: 10 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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-30",
+        last_updated: "2025-09-30",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15 },
+        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-08",
+        last_updated: "2025-08-08",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.25, output: 10 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "grok-4.20-beta-0309-reasoning": {
+        id: "grok-4.20-beta-0309-reasoning",
+        name: "grok-4.20-beta-0309-reasoning",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6 },
+        limit: { context: 2000000, output: 30000 },
+      },
+      "claude-opus-4-20250514": {
+        id: "claude-opus-4-20250514",
+        name: "claude-opus-4-20250514",
+        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 },
+        limit: { context: 200000, output: 32000 },
+      },
+      "glm-for-coding": {
+        id: "glm-for-coding",
+        name: "glm-for-coding",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-09-30",
+        last_updated: "2025-09-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.086, output: 0.343 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "glm-4.5-airx": {
+        id: "glm-4.5-airx",
+        name: "glm-4.5-airx",
+        family: "glm",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2025-07-29",
+        last_updated: "2025-07-29",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.572, output: 1.714 },
+        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,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2024-04",
+        release_date: "2025-04-14",
+        last_updated: "2025-04-14",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 8 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 1.6 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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.5-122b-a10b": {
+        id: "qwen3.5-122b-a10b",
+        name: "Qwen3.5 122B-A10B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-23",
+        last_updated: "2026-02-23",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.4, output: 3.2 },
+        limit: { context: 262144, 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: 1, output: 5 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "qwen3.6-27b": {
+        id: "qwen3.6-27b",
+        name: "Qwen3.6 27B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3.6 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "qwen3.5-27b": {
+        id: "qwen3.5-27b",
+        name: "Qwen3.5 27B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-23",
+        last_updated: "2026-02-23",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 2.4 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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 },
+      },
+      "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-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.5-397b-a17b": {
+        id: "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-15",
+        last_updated: "2026-02-15",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3.6 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "qwen3.6-35b-a3b": {
+        id: "qwen3.6-35b-a3b",
+        name: "Qwen3.6 35B-A3B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-17",
+        last_updated: "2026-04-17",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.248, output: 1.485 },
+        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-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-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 },
+      },
+      "qwen3.6-plus": {
+        id: "qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.276, output: 1.651, cache_read: 0.028, cache_write: 0.344 },
+        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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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-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-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 },
+      },
+      "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 },
+      },
+      "qwen3.6-max-preview": {
+        id: "qwen3.6-max-preview",
+        name: "Qwen3.6 Max Preview",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.3, output: 7.8, cache_read: 0.13, cache_write: 1.625 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.5-35b-a3b": {
+        id: "qwen3.5-35b-a3b",
+        name: "Qwen3.5 35B-A3B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-23",
+        last_updated: "2026-02-23",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 2 },
+        limit: { context: 262144, output: 65536 },
+      },
+    },
+  },
+  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: {
+      "qwen3-embedding-8b": {
+        id: "qwen3-embedding-8b",
+        name: "Qwen3 Embedding 8B",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-25-11",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.1, output: 0 },
+        limit: { context: 32768, output: 4096 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.75, output: 2.25 },
+        limit: { context: 260000, output: 16384 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.9, output: 0.9 },
+        limit: { context: 100000, 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-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3.6 },
+        limit: { context: 256000, 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-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.4, output: 2 },
+        limit: { context: 256000, output: 16384 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.9, output: 0.9 },
+        limit: { context: 32000, output: 8196 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.2 },
+        limit: { context: 128000, output: 4096 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.003, output: 0 },
+        limit: { context: 0, output: 8192 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.35 },
+        limit: { context: 32000, output: 16384 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 0.5 },
+        limit: { context: 40000, 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.1, output: 0 },
+        limit: { context: 8191, output: 3072 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.8 },
+        limit: { context: 128000, output: 32768 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.35 },
+        limit: { context: 128000, output: 32768 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.6 },
+        limit: { context: 128000, output: 32768 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.2 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "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: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.2 },
+        limit: { context: 128000, output: 16384 },
+      },
+    },
+  },
+  "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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "qwen-3.6-plus": {
+        id: "qwen-3.6-plus",
+        name: "Qwen 3.6 Plus",
+        family: "qwen3.6",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.45, output: 2.7 },
+        limit: { context: 991800, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "qwen3.6-max-preview": {
+        id: "qwen3.6-max-preview",
+        name: "Qwen3.6 Max Preview",
+        family: "qwen3.6",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.3, output: 7.8 },
+        limit: { context: 245800, 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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "alibaba/qwen3.6-flash": {
+        id: "alibaba/qwen3.6-flash",
+        name: "Qwen3.6 Flash",
+        family: "qwen3.6",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        release_date: "2026-04-17",
+        last_updated: "2026-04-17",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.19, output: 1.16 },
+        limit: { context: 991800, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "LLM360/K2-Think": {
+        id: "LLM360/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 },
+      },
+      "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/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/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/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/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/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-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/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/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/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/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/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/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/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 },
+      },
+      "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.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/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/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/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 },
+      },
+      "CrucibleLab/L3.3-70B-Loki-V2.0": {
+        id: "CrucibleLab/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "Doctor-Shotgun/MS3.2-24B-Magnum-Diamond": {
+        id: "Doctor-Shotgun/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 },
+      },
+      "NeverSleep/Llama-3-Lumimaid-70B-v0.1": {
+        id: "NeverSleep/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/Lumimaid-v0.2-70B": {
+        id: "NeverSleep/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 },
+      },
+      "Steelskull/L3.3-Cu-Mai-R1-70b": {
+        id: "Steelskull/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/L3.3-Nevoria-R1-70b": {
+        id: "Steelskull/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/L3.3-MS-Evayale-70B": {
+        id: "Steelskull/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 },
+      },
+      "Steelskull/L3.3-Electra-R1-70b": {
+        id: "Steelskull/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/L3.3-MS-Nevoria-70b": {
+        id: "Steelskull/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/L3.3-MS-Evalebis-70b": {
+        id: "Steelskull/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "Salesforce/Llama-xLAM-2-70b-fc-r": {
+        id: "Salesforce/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 },
+      },
+      "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/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: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 },
+      },
+      "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/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 },
+      },
+      "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/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 },
+      },
+      "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 },
+      },
+      "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/Llama-3.05-NT-Storybreaker-Ministral-70B": {
+        id: "Envoid/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 },
+      },
+      "Envoid/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B": {
+        id: "Envoid/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "ReadyArt/MS3.2-The-Omega-Directive-24B-Unslop-v2.0": {
+        id: "ReadyArt/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 },
+      },
+      "ReadyArt/The-Omega-Abomination-L-70B-v1.0": {
+        id: "ReadyArt/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 },
+      },
+      "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 },
+      },
+      "MarinaraSpaghetti/NemoMix-Unleashed-12B": {
+        id: "MarinaraSpaghetti/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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": {
+        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-5.1": {
+        id: "zai-org/glm-5.1",
+        name: "GLM 5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 2.55 },
+        limit: { context: 200000, input: 200000, output: 131072 },
+      },
+      "zai-org/glm-5.1:thinking": {
+        id: "zai-org/glm-5.1:thinking",
+        name: "GLM 5.1 Thinking",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 2.55 },
+        limit: { context: 200000, input: 200000, output: 131072 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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/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/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/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/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/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/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 },
+      },
+      "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-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/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-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-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-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/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/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 },
+      },
+      "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 },
+      },
+      "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-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-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.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.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-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 },
+      },
+      "GalrionSoftworks/MN-LooseCannon-12B-v1": {
+        id: "GalrionSoftworks/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 },
+      },
+      "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 },
+      },
+      "Gryphe/MythoMax-L2-13b": {
+        id: "Gryphe/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 },
+      },
+      "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-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 },
+      },
+      "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-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": {
+        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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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: true,
+        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 },
+      },
+      "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 },
+      },
+      "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/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-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/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 },
+      },
+      "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.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/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/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/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-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 },
+      },
+      "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-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 },
+      },
+      "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: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 },
+      },
+      "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-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-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-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 },
+      },
+      "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-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/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/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/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/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/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.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/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/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/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/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-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/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/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-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.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/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-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/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/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-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/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-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.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-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 },
+      },
+      "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/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/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-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-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-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-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/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-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/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/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-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-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-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/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/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/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 },
+      },
+      "VongolaChouko/Starcannon-Unleashed-12B-v1.0": {
+        id: "VongolaChouko/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 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "Sao10K/L3.3-70B-Euryale-v2.3": {
+        id: "Sao10K/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/L3.1-70B-Euryale-v2.2": {
+        id: "Sao10K/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/L3.1-70B-Hanami-x1": {
+        id: "Sao10K/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/L3-8B-Stheno-v3.2": {
+        id: "Sao10K/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 },
+      },
+      "LatitudeGames/Wayfarer-Large-70B-Llama-3.3": {
+        id: "LatitudeGames/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 },
+      },
+      "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": {
+        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 },
+      },
+      "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: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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "Infermatic/MN-12B-Inferor-v0.0": {
+        id: "Infermatic/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "minimax/minimax-m2.7": {
+        id: "minimax/minimax-m2.7",
+        name: "MiniMax M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3, output: 1.2 },
+        limit: { context: 204800, input: 204800, output: 131072 },
+      },
+      "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 },
+      },
+      "qwen/Qwen3.6-35B-A3B:thinking": {
+        id: "qwen/Qwen3.6-35B-A3B:thinking",
+        name: "Qwen3.6 35B A3B Thinking",
+        family: "qwen3.6",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        structured_output: false,
+        release_date: "2026-04-19",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.29, output: 1.74 },
+        limit: { context: 262144, output: 16384 },
+      },
+      "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 },
+      },
+      "qwen/Qwen3.6-35B-A3B": {
+        id: "qwen/Qwen3.6-35B-A3B",
+        name: "Qwen3.6 35B A3B",
+        family: "qwen3.6",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        release_date: "2026-04-17",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.29, output: 1.74 },
+        limit: { context: 262144, output: 16384 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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-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 },
+      },
+      "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-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-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 },
+      },
+      "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-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-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 },
+      },
+      "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-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.6": {
+        id: "moonshotai/kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: false,
+        release_date: "2026-04-16",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.53, output: 2.73 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "moonshotai/kimi-k2.6:thinking": {
+        id: "moonshotai/kimi-k2.6:thinking",
+        name: "Kimi K2.6 Thinking",
+        family: "kimi-thinking",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: false,
+        release_date: "2026-04-16",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.53, output: 2.73 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "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-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.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 },
+      },
+      "Tongyi-Zhiwen/QwenLong-L1-32B": {
+        id: "Tongyi-Zhiwen/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "Alibaba-NLP/Tongyi-DeepResearch-30B-A3B": {
+        id: "Alibaba-NLP/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 },
+      },
+      "MiniMaxAI/MiniMax-M1-80k": {
+        id: "MiniMaxAI/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 },
+      },
+      "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": {
+        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 },
+      },
+      "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,
+        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: 2.992, output: 14.993999999999998 },
+        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: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-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,
+        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: 2.992, output: 14.993999999999998 },
+        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 },
+      },
+      "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 },
+      },
+      "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.0": {
+        id: "EVA-UNIT-01/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/EVA-Qwen2.5-72B-v0.2": {
+        id: "EVA-UNIT-01/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/EVA-LLaMA-3.33-70B-v0.1": {
+        id: "EVA-UNIT-01/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/EVA-Qwen2.5-32B-v0.2": {
+        id: "EVA-UNIT-01/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 },
+      },
+      "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/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/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/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/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 },
+      },
+      "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 },
+      },
+      "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": {
+        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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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.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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+        limit: { context: 200000, output: 128000 },
+      },
+      "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-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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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": {
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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/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/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "openai/gpt-5.5": {
+        id: "openai/gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5 },
+        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 },
+      },
+      "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.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.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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "anthropic/claude-opus-4-7": {
+        id: "anthropic/claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 25, cache_read: 0.5 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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-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-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 },
+        limit: { context: 200000, 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 },
+        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: {
+      "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 },
+      },
+      "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-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-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-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/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.6-35B-A3B": {
+        id: "Qwen/Qwen3.6-35B-A3B",
+        name: "Qwen/Qwen3.6-35B-A3B",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-17",
+        last_updated: "2026-04-17",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.23, output: 1.86 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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/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-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-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-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/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/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-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/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-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-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/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-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-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-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-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-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-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-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-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-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-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-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/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-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/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-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-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-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-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/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 },
+      },
+      "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 },
+      },
+      "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.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-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 },
+      },
+      "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/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.1": {
+        id: "Pro/zai-org/GLM-5.1",
+        name: "Pro/zai-org/GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_write: 0 },
+        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/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-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.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-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 },
+      },
+      "Pro/moonshotai/Kimi-K2.6": {
+        id: "Pro/moonshotai/Kimi-K2.6",
+        name: "Pro/moonshotai/Kimi-K2.6",
+        family: "kimi",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262000, output: 262000 },
+      },
+      "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,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        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.25 },
+        limit: { context: 262000, output: 262000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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-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": {
+        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-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-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-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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-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": {
+        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": {
+        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.7": {
+        id: "MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        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.7-highspeed": {
+        id: "MiniMax-M2.7-highspeed",
+        name: "MiniMax-M2.7-highspeed",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        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 },
+      },
+      "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 },
+      },
+    },
+  },
+  perplexity: {
+    id: "perplexity",
+    env: ["PERPLEXITY_API_KEY"],
+    npm: "@ai-sdk/perplexity",
+    name: "Perplexity",
+    doc: "https://docs.perplexity.ai",
+    models: {
+      "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 },
+      },
+      "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: {
+        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-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 },
+      },
+    },
+  },
+  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-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.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "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.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "deepseek-v4-flash": {
+        id: "deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1000000, output: 384000 },
+      },
+    },
+  },
+  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: {
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-r1": {
+        id: "deepseek/deepseek-r1",
+        name: "DeepSeek: R1",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: 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: 0.7, output: 2.5 },
+        limit: { context: 64000, output: 16000 },
+      },
+      "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-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.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-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-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 },
+      },
+      "openrouter/owl-alpha": {
+        id: "openrouter/owl-alpha",
+        name: "Owl Alpha",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 1048756, output: 262144 },
+        status: "alpha",
+      },
+      "openrouter/pareto-code": {
+        id: "openrouter/pareto-code",
+        name: "Pareto Code Router",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        limit: { context: 200000, output: 200000 },
+      },
+      "openrouter/elephant-alpha": {
+        id: "openrouter/elephant-alpha",
+        name: "Elephant (free)",
+        family: "elephant",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-13",
+        last_updated: "2026-04-13",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 32768 },
+      },
+      "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 },
+      },
+      "arcee-ai/trinity-large-thinking": {
+        id: "arcee-ai/trinity-large-thinking",
+        name: "Trinity Large Thinking",
+        family: "trinity",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.22, output: 0.85 },
+        limit: { context: 262144, output: 80000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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/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/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 },
+      },
+      "mistralai/mistral-small-2603": {
+        id: "mistralai/mistral-small-2603",
+        name: "Mistral Small 4",
+        family: "mistral-small",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-06",
+        release_date: "2026-03-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.6 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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/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-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/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-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 },
+      },
+      "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.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 },
+      },
+      "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-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-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-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 },
+      },
+      "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-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.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-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-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": {
+        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 },
+      },
+      "tencent/hy3-preview": {
+        id: "tencent/hy3-preview",
+        name: "Hy3 preview",
+        family: "Hy",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.066, output: 0.26, cache_read: 0.029, cache_write: 0.029 },
+        limit: { context: 256000, output: 64000 },
+      },
+      "poolside/laguna-m.1:free": {
+        id: "poolside/laguna-m.1:free",
+        name: "Laguna M.1",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-04-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "poolside/laguna-xs.2:free": {
+        id: "poolside/laguna-xs.2:free",
+        name: "Laguna XS.2",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-04-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "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 },
+      },
+      "nvidia/nemotron-3-super-120b-a12b": {
+        id: "nvidia/nemotron-3-super-120b-a12b",
+        name: "Nemotron 3 Super",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-04",
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.5 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free": {
+        id: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
+        name: "Nemotron 3 Nano Omni (free)",
+        family: "nemotron",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-04-28",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "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: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-3-super-120b-a12b:free": {
+        id: "nvidia/nemotron-3-super-120b-a12b:free",
+        name: "Nemotron 3 Super (free)",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-04",
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "inception/mercury-edit-2": {
+        id: "inception/mercury-edit-2",
+        name: "Mercury Edit 2",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-30",
+        last_updated: "2026-03-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 0.75, cache_read: 0.025 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "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 },
+      },
+      "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-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-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-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.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-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 },
+      },
+      "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.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-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.4-mini": {
+        id: "openai/gpt-5.4-mini",
+        name: "GPT-5.4 Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        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/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-5.4-nano": {
+        id: "openai/gpt-5.4-nano",
+        name: "GPT-5.4 Nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        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-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-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": {
+        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.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-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-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-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-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": {
+        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/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-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-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-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.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 },
+      },
+      "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.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-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-5.1": {
+        id: "z-ai/glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        limit: { context: 202752, output: 131072 },
+      },
+      "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.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-5-turbo": {
+        id: "z-ai/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-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.96, output: 3.2, cache_read: 0.192, cache_write: 0 },
+        limit: { context: 202752, 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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "minimax/minimax-m2.7": {
+        id: "minimax/minimax-m2.7",
+        name: "MiniMax M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06, cache_write: 0.375 },
+        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-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.5:free": {
+        id: "minimax/minimax-m2.5:free",
+        name: "MiniMax M2.5 (free)",
+        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, output: 0 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "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-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 },
+      },
+      "qwen/qwen3-coder-plus": {
+        id: "qwen/qwen3-coder-plus",
+        name: "Qwen3 Coder Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2025-09-23",
+        last_updated: "2025-09-23",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.65, output: 3.25, cache_read: 0.13, cache_write: 0.8125 },
+        limit: { context: 1000000, 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,
+        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/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/qwen-plus": {
+        id: "qwen/qwen-plus",
+        name: "Qwen: Qwen-Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: false,
+        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.26, output: 0.78, cache_read: 0.052, cache_write: 0.325 },
+        limit: { context: 1000000, output: 32768 },
+      },
+      "qwen/qwen3.5-flash-02-23": {
+        id: "qwen/qwen3.5-flash-02-23",
+        name: "Qwen: Qwen3.5-Flash",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-25",
+        last_updated: "2026-02-25",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.065, output: 0.26 },
+        limit: { context: 1000000, output: 65536 },
+      },
+      "qwen/qwen3.6-plus": {
+        id: "qwen/qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.325, output: 1.95, cache_read: 0.0325, cache_write: 0.40625 },
+        limit: { context: 1000000, output: 65536 },
+      },
+      "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, cache_read: 0.156, cache_write: 0.975 },
+        limit: { context: 262144, output: 32768 },
+      },
+      "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/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/qwen-3.6-27b": {
+        id: "qwen/qwen-3.6-27b",
+        name: "Qwen3.6 27B",
+        family: "qwen",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.195, output: 1.56 },
+        limit: { context: 262144, output: 81920 },
+      },
+      "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-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/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/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, cache_read: 0.039, cache_write: 0.24375 },
+        limit: { context: 128000, output: 66536 },
+      },
+      "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/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-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-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/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-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 },
+      },
+      "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/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/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/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-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/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.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/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-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-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/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-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/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-4-31b-it": {
+        id: "google/gemma-4-31b-it",
+        name: "Gemma 4 31B",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.4 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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-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-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 },
+      },
+      "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-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-flash",
+        attachment: true,
+        reasoning: true,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-02-26",
+        last_updated: "2026-02-26",
+        modalities: { input: ["text", "image"], output: ["text", "image"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 3 },
+        limit: { context: 65536, output: 65536 },
+      },
+      "google/gemma-4-31b-it:free": {
+        id: "google/gemma-4-31b-it:free",
+        name: "Gemma 4 31B (free)",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 32768 },
+      },
+      "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/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/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/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/gemma-4-26b-a4b-it": {
+        id: "google/gemma-4-26b-a4b-it",
+        name: "Gemma 4 26B A4B",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-03",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.13, output: 0.4 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "google/gemma-4-26b-a4b-it:free": {
+        id: "google/gemma-4-26b-a4b-it:free",
+        name: "Gemma 4 26B A4B (free)",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-03",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 32768 },
+      },
+      "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 },
+      },
+      "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-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.6": {
+        id: "moonshotai/kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_details" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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": {
+        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-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 },
+      },
+      "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-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.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-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-opus-4.7": {
+        id: "anthropic/claude-opus-4.7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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": {
+        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.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-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-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,
+        knowledge: "2025-08-31",
+        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 },
+      },
+      "deepseek/deepseek-v4-flash": {
+        id: "deepseek/deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1048576, output: 393216 },
+      },
+      "deepseek/deepseek-v4-pro": {
+        id: "deepseek/deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1048576, output: 393216 },
+      },
+      "x-ai/grok-4.3": {
+        id: "x-ai/grok-4.3",
+        name: "Grok 4.3",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-05-01",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 1.25,
+          output: 2.5,
+          cache_read: 0.2,
+          context_over_200k: { input: 2.5, output: 5, cache_read: 0.4 },
+        },
+        limit: { context: 1000000, output: 1000000 },
+      },
+      "openai/gpt-5.5": {
+        id: "openai/gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+      },
+      "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.5-pro": {
+        id: "openai/gpt-5.5-pro",
+        name: "GPT-5.5 Pro",
+        family: "gpt-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 30, output: 180, context_over_200k: { input: 60, output: 270 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+      },
+      "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.125,
+          context_over_200k: { input: 2.5, output: 15, cache_read: 0.25 },
+        },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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 },
+      },
+      "xiaomi/mimo-v2.5-pro": {
+        id: "xiaomi/mimo-v2.5-pro",
+        name: "Xiaomi: MiMo-V2.5-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "xiaomi/mimo-v2-omni": {
+        id: "xiaomi/mimo-v2-omni",
+        name: "Xiaomi: MiMo-V2-Omni",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_details" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 2, cache_read: 0.08 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "xiaomi/mimo-v2.5": {
+        id: "xiaomi/mimo-v2.5",
+        name: "Xiaomi: MiMo-V2.5",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_details" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: {
+          input: 0.4,
+          output: 2,
+          cache_read: 0.08,
+          context_over_200k: { input: 0.8, output: 4, cache_read: 0.16 },
+        },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "xiaomi/mimo-v2-pro": {
+        id: "xiaomi/mimo-v2-pro",
+        name: "Xiaomi: MiMo-V2-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_details" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "xiaomi/mimo-v2-flash": {
+        id: "xiaomi/mimo-v2-flash",
+        name: "Xiaomi: MiMo-V2-Flash",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_details" },
+        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: 262144, output: 65536 },
+      },
+    },
+  },
+  "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/glm-5p1": {
+        id: "accounts/fireworks/models/glm-5p1",
+        name: "GLM 5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        limit: { context: 202800, 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/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/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/kimi-k2p6": {
+        id: "accounts/fireworks/models/kimi-k2p6",
+        name: "Kimi K2.6",
+        family: "kimi-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-17",
+        last_updated: "2026-04-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262000, output: 262000 },
+      },
+      "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/qwen3p6-plus": {
+        id: "accounts/fireworks/models/qwen3p6-plus",
+        name: "Qwen 3.6 Plus",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-04",
+        last_updated: "2026-04-04",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 3, cache_read: 0.1 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "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/minimax-m2p7": {
+        id: "accounts/fireworks/models/minimax-m2p7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-12",
+        last_updated: "2026-04-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/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-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/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/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 },
+      },
+      "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-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/routers/kimi-k2p5-turbo": {
+        id: "accounts/fireworks/routers/kimi-k2p5-turbo",
+        name: "Kimi K2.5 Turbo",
+        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"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "accounts/fireworks/models/deepseek-v4-pro": {
+        id: "accounts/fireworks/models/deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.15 },
+        limit: { context: 1000000, output: 384000 },
+      },
+    },
+  },
+  "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: {
+      k2p6: {
+        id: "k2p6",
+        name: "Kimi K2.6",
+        family: "kimi-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04",
+        last_updated: "2026-04",
+        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 },
+      },
+      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 },
+      },
+    },
+  },
+  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 },
+      },
+    },
+  },
+  "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: {
+      "minimax-m2.7": {
+        id: "minimax-m2.7",
+        name: "MiniMax M2.7",
+        family: "minimax-m2.7",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        limit: { context: 204800, output: 131072 },
+        provider: { npm: "@ai-sdk/anthropic" },
+      },
+      "kimi-k2.5": {
+        id: "kimi-k2.5",
+        name: "Kimi K2.5",
+        family: "kimi-k2.5",
+        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 },
+      },
+      "mimo-v2.5-pro": {
+        id: "mimo-v2.5-pro",
+        name: "MiMo V2.5 Pro",
+        family: "mimo-v2.5-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, 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,
+        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: 202752, output: 32768 },
+      },
+      "mimo-v2-omni": {
+        id: "mimo-v2-omni",
+        name: "MiMo V2 Omni",
+        family: "mimo-v2-omni",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "pdf"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.4, output: 2, cache_read: 0.08 },
+        limit: { context: 262144, output: 128000 },
+        status: "deprecated",
+      },
+      "mimo-v2.5": {
+        id: "mimo-v2.5",
+        name: "MiMo V2.5",
+        family: "mimo-v2.5",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: {
+          input: 0.4,
+          output: 2,
+          cache_read: 0.08,
+          context_over_200k: { input: 0.8, output: 4, cache_read: 0.16 },
+        },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "qwen3.6-plus": {
+        id: "qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen3.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 3, cache_read: 0.05, cache_write: 0.625 },
+        limit: { context: 262144, output: 65536 },
+        provider: { npm: "@ai-sdk/anthropic" },
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        limit: { context: 202752, output: 32768 },
+      },
+      "deepseek-v4-flash": {
+        id: "deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.0028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-10",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.0145 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "minimax-m2.5": {
+        id: "minimax-m2.5",
+        name: "MiniMax M2.5",
+        family: "minimax-m2.5",
+        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.03 },
+        limit: { context: 204800, output: 65536 },
+      },
+      "mimo-v2-pro": {
+        id: "mimo-v2-pro",
+        name: "MiMo V2 Pro",
+        family: "mimo-v2-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 128000 },
+        status: "deprecated",
+      },
+      "qwen3.5-plus": {
+        id: "qwen3.5-plus",
+        name: "Qwen3.5 Plus",
+        family: "qwen3.5",
+        attachment: true,
+        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.2, output: 1.2, cache_read: 0.02, cache_write: 0.25 },
+        limit: { context: 262144, output: 65536 },
+        provider: { npm: "@ai-sdk/anthropic" },
+      },
+    },
+  },
+  "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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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/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/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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.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 },
+      },
+      "qwen3.6-plus": {
+        id: "qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.276, output: 1.651, cache_read: 0.028, cache_write: 0.344 },
+        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 },
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-14",
+        last_updated: "2026-04-14",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.87, output: 3.48, cache_read: 0.17 },
+        limit: { context: 202752, output: 128000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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-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-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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Moonshot Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: false,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.929, output: 3.858 },
+        limit: { context: 262144, output: 16384 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "qwen3.6-max-preview": {
+        id: "qwen3.6-max-preview",
+        name: "Qwen3.6 Max Preview",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.32, output: 7.9, cache_read: 0.132 },
+        limit: { context: 245800, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "MiniMax/MiniMax-M2.7": {
+        id: "MiniMax/MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06, cache_write: 0.375 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "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.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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "deepseek-v4-flash": {
+        id: "deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1000000, output: 384000 },
+      },
+    },
+  },
+  firepass: {
+    id: "firepass",
+    env: ["FIREPASS_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.fireworks.ai/inference/v1/",
+    name: "Fireworks (Firepass)",
+    doc: "https://docs.fireworks.ai/firepass",
+    models: {
+      "accounts/fireworks/routers/kimi-k2p6-turbo": {
+        id: "accounts/fireworks/routers/kimi-k2p6-turbo",
+        name: "Kimi K2.6 Turbo",
+        family: "kimi-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-17",
+        last_updated: "2026-04-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 262000, output: 262000 },
+      },
+    },
+  },
+  "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": {
+        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": {
+        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.7": {
+        id: "MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        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.7-highspeed": {
+        id: "MiniMax-M2.7-highspeed",
+        name: "MiniMax-M2.7-highspeed",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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.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-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 },
+      },
+      "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 },
+      },
+      "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.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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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.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 },
+      },
+      "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-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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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,
+        knowledge: "2025-05-31",
+        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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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-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-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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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-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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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.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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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-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-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-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-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-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 },
+      },
+      "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-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 },
+      },
+      "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.1": {
+        id: "zai-org/GLM-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-03",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3.2, cache_read: 0.2 },
+        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: 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 },
+      },
+      "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-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 },
+      },
+      "moonshotai/Kimi-K2.6": {
+        id: "moonshotai/Kimi-K2.6",
+        name: "Kimi-K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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.7": {
+        id: "MiniMaxAI/MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        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-V4-Pro": {
+        id: "deepseek-ai/DeepSeek-V4-Pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1048576, output: 393216 },
+      },
+    },
+  },
+  zenmux: {
+    id: "zenmux",
+    env: ["ZENMUX_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://zenmux.ai/api/v1",
+    name: "ZenMux",
+    doc: "https://docs.zenmux.ai",
+    models: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "kuaishou/kat-coder-pro-v2": {
+        id: "kuaishou/kat-coder-pro-v2",
+        name: "KAT-Coder-Pro-V2",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-30",
+        last_updated: "2026-03-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        limit: { context: 256000, output: 80000 },
+      },
+      "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-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.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": {
+        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": {
+        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 },
+      },
+      "x-ai/grok-4.2-fast": {
+        id: "x-ai/grok-4.2-fast",
+        name: "Grok 4.2 Fast",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 9 },
+        limit: { context: 2000000, output: 30000 },
+      },
+      "x-ai/grok-4.2-fast-non-reasoning": {
+        id: "x-ai/grok-4.2-fast-non-reasoning",
+        name: "Grok 4.2 Fast Non Reasoning",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 9 },
+        limit: { context: 2000000, output: 30000 },
+      },
+      "openai/gpt-5.3-chat": {
+        id: "openai/gpt-5.3-chat",
+        name: "GPT-5.3 Chat",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.75, output: 14 },
+        limit: { context: 128000, output: 16380 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "openai/gpt-5.3-codex": {
+        id: "openai/gpt-5.3-codex",
+        name: "GPT-5.3 Codex",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.75, output: 14 },
+        limit: { context: 400000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "openai/gpt-5.4-mini": {
+        id: "openai/gpt-5.4-mini",
+        name: "GPT-5.4 Mini",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5 },
+        limit: { context: 400000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "openai/gpt-5.4-nano": {
+        id: "openai/gpt-5.4-nano",
+        name: "GPT-5.4 Nano",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25 },
+        limit: { context: 400000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "openai/gpt-5.4-pro": {
+        id: "openai/gpt-5.4-pro",
+        name: "GPT-5.4 Pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 45, output: 225 },
+        limit: { context: 1050000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "openai/gpt-5.4": {
+        id: "openai/gpt-5.4",
+        name: "GPT-5.4",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3.75, output: 18.75 },
+        limit: { context: 1050000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai", api: "https://zenmux.ai/api/v1" },
+      },
+      "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,
+        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, output: 0 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "z-ai/glm-5v-turbo": {
+        id: "z-ai/glm-5v-turbo",
+        name: "GLM 5V Turbo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.726, output: 3.1946, cache_read: 0.1743 },
+        limit: { context: 200000, output: 128000 },
+      },
+      "z-ai/glm-4.7": {
+        id: "z-ai/glm-4.7",
+        name: "GLM 4.7",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        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-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.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-5.1": {
+        id: "z-ai/glm-5.1",
+        name: "GLM-5.1",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-03",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.8781, output: 3.5126, cache_read: 0.1903 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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.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.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-5-turbo": {
+        id: "z-ai/glm-5-turbo",
+        name: "GLM 5 Turbo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01-01",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.88, output: 3.48 },
+        limit: { context: 200000, output: 128000 },
+      },
+      "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.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 },
+      },
+      "volcengine/doubao-seed-2.0-code": {
+        id: "volcengine/doubao-seed-2.0-code",
+        name: "Doubao Seed 2.0 Code",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01-01",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.9, output: 4.48 },
+        limit: { context: 256000, output: 32000 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "minimax/minimax-m2.7": {
+        id: "minimax/minimax-m2.7",
+        name: "MiniMax M2.7",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01-01",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3055, output: 1.2219 },
+        limit: { context: 204800, output: 131070 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "minimax/minimax-m2.7-highspeed": {
+        id: "minimax/minimax-m2.7-highspeed",
+        name: "MiniMax M2.7 highspeed",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01-01",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.611, output: 2.4439 },
+        limit: { context: 204800, output: 131070 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "minimax/minimax-m2.5-lightning": {
+        id: "minimax/minimax-m2.5-lightning",
+        name: "MiniMax M2.5 highspeed",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "minimax/minimax-m2.5": {
+        id: "minimax/minimax-m2.5",
+        name: "MiniMax M2.5",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+      },
+      "qwen/qwen3.5-flash": {
+        id: "qwen/qwen3.5-flash",
+        name: "Qwen3.5 Flash",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01-01",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.1, output: 0.4 },
+        limit: { context: 1020000, output: 1020000 },
+      },
+      "qwen/qwen3.6-plus": {
+        id: "qwen/qwen3.6-plus",
+        name: "Qwen3.6-Plus",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-30",
+        last_updated: "2026-03-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 0.5,
+          output: 3,
+          cache_read: 0.05,
+          cache_write: 0.625,
+          context_over_200k: { input: 2, output: 6, cache_read: 0.2, cache_write: 2.5 },
+        },
+        limit: { context: 1000000, 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.5-plus": {
+        id: "qwen/qwen3.5-plus",
+        name: "Qwen3.5 Plus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01-01",
+        release_date: "2026-03-20",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.8, output: 4.8 },
+        limit: { context: 1000000, output: 64000 },
+      },
+      "google/gemini-3.1-flash-lite-preview": {
+        id: "google/gemini-3.1-flash-lite-preview",
+        name: "Gemini 3.1 Flash Lite Preview",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-03-20",
+        last_updated: "2025-03-20",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 1.5 },
+        limit: { context: 1050000, output: 65530 },
+      },
+      "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-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-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 },
+      },
+      "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-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 },
+      },
+      "sapiens-ai/agnes-1.5-lite": {
+        id: "sapiens-ai/agnes-1.5-lite",
+        name: "Agnes 1.5 Lite",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-26",
+        last_updated: "2026-03-26",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.12, output: 0.6 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "sapiens-ai/agnes-1.5-pro": {
+        id: "sapiens-ai/agnes-1.5-pro",
+        name: "Agnes 1.5 Pro",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-21",
+        last_updated: "2026-03-21",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.16, output: 0.8 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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: false,
+        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-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 },
+      },
+      "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.6": {
+        id: "moonshotai/kimi-k2.6",
+        name: "Kimi K2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: false,
+        knowledge: "2025-01-01",
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262140, output: 262140 },
+      },
+      "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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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-05-31",
+        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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "anthropic/claude-opus-4.7": {
+        id: "anthropic/claude-opus-4.7",
+        name: "Claude Opus 4.7",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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: 32000 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "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-08-31",
+        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 },
+        provider: { npm: "@ai-sdk/anthropic", api: "https://zenmux.ai/api/anthropic/v1" },
+      },
+      "deepseek/deepseek-v4-flash": {
+        id: "deepseek/deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "deepseek/deepseek-v4-pro": {
+        id: "deepseek/deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "tencent/hy3-preview": {
+        id: "tencent/hy3-preview",
+        name: "Hy3 preview",
+        family: "Hy",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.172, output: 0.572, cache_read: 0.058, cache_write: 0 },
+        limit: { context: 256000, output: 64000 },
+      },
+      "openai/gpt-5.5": {
+        id: "openai/gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+        experimental: {
+          modes: {
+            fast: {
+              cost: { input: 12.5, output: 75, cache_read: 1.25 },
+              provider: { body: { service_tier: "priority" } },
+            },
+          },
+        },
+      },
+      "openai/gpt-5.5-pro": {
+        id: "openai/gpt-5.5-pro",
+        name: "GPT-5.5 Pro",
+        family: "gpt-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 30, output: 180, context_over_200k: { input: 60, output: 270 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+      },
+      "xiaomi/mimo-v2.5-pro": {
+        id: "xiaomi/mimo-v2.5-pro",
+        name: "MiMo-V2.5-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "xiaomi/mimo-v2-omni": {
+        id: "xiaomi/mimo-v2-omni",
+        name: "MiMo V2 Omni",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 2, cache_read: 0.08 },
+        limit: { context: 265000, output: 265000 },
+      },
+      "xiaomi/mimo-v2.5": {
+        id: "xiaomi/mimo-v2.5",
+        name: "MiMo-V2.5",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: {
+          input: 0.4,
+          output: 2,
+          cache_read: 0.08,
+          context_over_200k: { input: 0.8, output: 4, cache_read: 0.16 },
+        },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "xiaomi/mimo-v2-pro": {
+        id: "xiaomi/mimo-v2-pro",
+        name: "MiMo V2 Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1000000, output: 256000 },
+      },
+      "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-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: 262144, output: 65536 },
+      },
+    },
+  },
+  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 },
+      },
+    },
+  },
+  "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: {
+      "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-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-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-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-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-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-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-qwen-32b": {
+        id: "deepseek/deepseek-r1-distill-qwen-32b",
+        name: "DeepSeek R1 Distill Qwen 32B",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-01-20",
+        last_updated: "2025-01-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 0.3 },
+        limit: { context: 64000, output: 32000 },
+      },
+      "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 },
+      },
+      "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-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-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-r1-distill-qwen-14b": {
+        id: "deepseek/deepseek-r1-distill-qwen-14b",
+        name: "DeepSeek R1 Distill Qwen 14B",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-01-20",
+        last_updated: "2025-01-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.15 },
+        limit: { context: 32768, output: 16384 },
+      },
+      "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-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 },
+      },
+      "inclusionai/ling-2.6-1t": {
+        id: "inclusionai/ling-2.6-1t",
+        name: "Ling-2.6-1T",
+        family: "ling",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 32768 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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/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-5.1": {
+        id: "zai-org/glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        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,
+        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.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.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.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.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 },
+      },
+      "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/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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-3.2-3b-instruct": {
+        id: "meta-llama/llama-3.2-3b-instruct",
+        name: "Llama 3.2 3B Instruct",
+        family: "llama",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2024-09-18",
+        last_updated: "2024-09-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.03, output: 0.05 },
+        limit: { context: 32768, output: 32000 },
+      },
+      "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-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-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-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 },
+      },
+      "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 },
+      },
+      "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-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/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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-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 },
+      },
+      "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 },
+      },
+      "minimax/minimax-m2.7": {
+        id: "minimax/minimax-m2.7",
+        name: "MiniMax M2.7",
+        family: "minimax-m2.7",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        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.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.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 },
+      },
+      "minimax/minimax-m2.5-highspeed": {
+        id: "minimax/minimax-m2.5-highspeed",
+        name: "MiniMax M2.5 Highspeed",
+        family: "minimax-m2.5",
+        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.6, output: 2.4, cache_read: 0.03 },
+        limit: { context: 204800, output: 131100 },
+      },
+      "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.5-122b-a10b": {
+        id: "qwen/qwen3.5-122b-a10b",
+        name: "Qwen3.5-122B-A10B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-26",
+        last_updated: "2026-02-26",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.4, output: 3.2 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "qwen/qwen3.6-27b": {
+        id: "qwen/qwen3.6-27b",
+        name: "Qwen3.6-27B",
+        family: "qwen3.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.6, output: 3.6 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "qwen/qwen3.5-27b": {
+        id: "qwen/qwen3.5-27b",
+        name: "Qwen3.5-27B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-26",
+        last_updated: "2026-02-26",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 2.4 },
+        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,
+        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-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/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-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-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-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 },
+      },
+      "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-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-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-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/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/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-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-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-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-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-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-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-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-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-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.5-35b-a3b": {
+        id: "qwen/qwen3.5-35b-a3b",
+        name: "Qwen3.5-35B-A3B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-26",
+        last_updated: "2026-02-26",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 2 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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 },
+      },
+      "google/gemma-4-31b-it": {
+        id: "google/gemma-4-31b-it",
+        name: "Gemma 4 31B",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.4 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "google/gemma-3-12b-it": {
+        id: "google/gemma-3-12b-it",
+        name: "Gemma 3 12B",
+        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.05, output: 0.1 },
+        limit: { context: 131072, 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,
+        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 },
+      },
+      "google/gemma-4-26b-a4b-it": {
+        id: "google/gemma-4-26b-a4b-it",
+        name: "Gemma 4 26B A4B",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.13, output: 0.4 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "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-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.6": {
+        id: "moonshotai/kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        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 },
+      },
+      "deepseek/deepseek-v4-flash": {
+        id: "deepseek/deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1048576, output: 393216 },
+      },
+      "deepseek/deepseek-v4-pro": {
+        id: "deepseek/deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.69, output: 3.38, cache_read: 0.13 },
+        limit: { context: 1048576, output: 393216 },
+      },
+    },
+  },
+  "xiaomi-token-plan-cn": {
+    id: "xiaomi-token-plan-cn",
+    env: ["XIAOMI_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://token-plan-cn.xiaomimimo.com/v1",
+    name: "Xiaomi Token Plan (China)",
+    doc: "https://platform.xiaomimimo.com/#/docs",
+    models: {
+      "mimo-v2-tts": {
+        id: "mimo-v2-tts",
+        name: "MiMo-V2-TTS",
+        family: "mimo",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["audio"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 8192, output: 16384 },
+      },
+      "mimo-v2.5-pro": {
+        id: "mimo-v2.5-pro",
+        name: "MiMo-V2.5-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 0, output: 0, cache_read: 0 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "mimo-v2-omni": {
+        id: "mimo-v2-omni",
+        name: "MiMo-V2-Omni",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "mimo-v2.5": {
+        id: "mimo-v2.5",
+        name: "MiMo-V2.5",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 0, output: 0, cache_read: 0 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "mimo-v2-pro": {
+        id: "mimo-v2-pro",
+        name: "MiMo-V2-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "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, output: 0, cache_read: 0 },
+        limit: { context: 262144, 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: {
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "zai-org/GLM-5.1": {
+        id: "zai-org/GLM-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26, cache_write: 0 },
+        limit: { context: 200000, 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: {
+      "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 },
+      },
+      "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-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.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/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-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 },
+      },
+      "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-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-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-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-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-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-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/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-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/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 },
+      },
+      "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.39, output: 2.34, cache_read: 0.195 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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/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/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 },
+      },
+      "zai-org/GLM-5.1-TEE": {
+        id: "zai-org/GLM-5.1-TEE",
+        name: "GLM 5.1 TEE",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 3.15, cache_read: 0.475 },
+        limit: { context: 202752, output: 65535 },
+      },
+      "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.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-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-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-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-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.95, output: 3.15, cache_read: 0.475 },
+        limit: { context: 202752, output: 65535 },
+      },
+      "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-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.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, cache_read: 0.15 },
+        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 },
+      },
+      "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.4, output: 1.7, cache_read: 0.2 },
+        limit: { context: 202752, 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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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-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 },
+      },
+      "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.28, output: 0.42, cache_read: 0.14 },
+        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 },
+      },
+      "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 },
+      },
+      "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/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 },
+      },
+      "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/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/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-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 },
+      },
+      "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 },
+      },
+      "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.6-TEE": {
+        id: "moonshotai/Kimi-K2.6-TEE",
+        name: "Kimi K2.6 TEE",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-12",
+        release_date: "2026-04-20",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.44, output: 2 },
+        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 },
+      },
+      "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 },
+      },
+      "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.3, output: 1.1, cache_read: 0.15 },
+        limit: { context: 196608, 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 },
+      },
+      "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/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/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/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 },
+      },
+    },
+  },
+  dinference: {
+    id: "dinference",
+    env: ["DINFERENCE_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.dinference.com/v1",
+    name: "DInference",
+    doc: "https://dinference.com",
+    models: {
+      "gpt-oss-120b": {
+        id: "gpt-oss-120b",
+        name: "GPT OSS 120B",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-08",
+        last_updated: "2025-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.0675, output: 0.27 },
+        limit: { context: 131072, 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.45, output: 1.65 },
+        limit: { context: 200000, 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: true,
+        cost: { input: 0.75, output: 2.4 },
+        limit: { context: 200000, output: 128000 },
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.25, output: 3.89 },
+        limit: { context: 200000, 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.22, output: 0.88 },
+        limit: { context: 200000, output: 32000 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai-compatible" },
+      },
+      "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 },
+        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" },
+      },
+      "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.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "GPT-5.4 Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai-compatible" },
+      },
+      "gpt-5.4-nano": {
+        id: "gpt-5.4-nano",
+        name: "GPT-5.4 Nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai-compatible" },
+      },
+      "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 },
+      },
+      "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 },
+        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 },
+      },
+      "gpt-5.5": {
+        id: "gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai-compatible" },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1000000, output: 384000 },
+        provider: { npm: "@ai-sdk/openai-compatible" },
+      },
+    },
+  },
+  deepinfra: {
+    id: "deepinfra",
+    env: ["DEEPINFRA_API_KEY"],
+    npm: "@ai-sdk/deepinfra",
+    name: "Deep Infra",
+    doc: "https://deepinfra.com/models",
+    models: {
+      "Qwen/Qwen3.5-397B-A17B": {
+        id: "Qwen/Qwen3.5-397B-A17B",
+        name: "Qwen 3.5 397B A17B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-02-01",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.54, output: 3.4 },
+        limit: { context: 262144, output: 81920 },
+      },
+      "Qwen/Qwen3.5-35B-A3B": {
+        id: "Qwen/Qwen3.5-35B-A3B",
+        name: "Qwen 3.5 35B A3B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-02-01",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.95 },
+        limit: { context: 262144, output: 81920 },
+      },
+      "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 },
+      },
+      "Qwen/Qwen3.6-35B-A3B": {
+        id: "Qwen/Qwen3.6-35B-A3B",
+        name: "Qwen3.6 35B A3B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 1 },
+        limit: { context: 262144, output: 81920 },
+      },
+      "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.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-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-5.1": {
+        id: "zai-org/GLM-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        limit: { context: 202752, output: 16384 },
+      },
+      "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 },
+      },
+      "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.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 },
+      },
+      "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-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-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-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.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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "moonshotai/Kimi-K2.6": {
+        id: "moonshotai/Kimi-K2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2024-04",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.75, output: 3.5, cache_read: 0.15 },
+        limit: { context: 262144, output: 16384 },
+      },
+      "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 },
+      },
+      "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.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.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 },
+      },
+      "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 },
+      },
+      "deepseek-ai/DeepSeek-V4-Flash": {
+        id: "deepseek-ai/DeepSeek-V4-Flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "deepseek-ai/DeepSeek-V4-Pro": {
+        id: "deepseek-ai/DeepSeek-V4-Pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 65536, output: 65536 },
+      },
+      "google/gemma-4-26B-A4B-it": {
+        id: "google/gemma-4-26B-A4B-it",
+        name: "Gemma 4 26B",
+        family: "gemma",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.07, output: 0.34 },
+        limit: { context: 256000, output: 8192 },
+      },
+      "google/gemma-4-31B-it": {
+        id: "google/gemma-4-31B-it",
+        name: "Gemma 4 31B",
+        family: "gemma",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.13, output: 0.38 },
+        limit: { context: 256000, output: 8192 },
+      },
+    },
+  },
+  "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: {
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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.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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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/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-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 },
+      },
+      "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.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.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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "mimo-v2-flash": {
+        id: "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-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: 256000 },
+      },
+      "xiaomi/mimo-v2-flash": {
+        id: "xiaomi/mimo-v2-flash",
+        name: "Xiaomi/Mimo-V2-Flash",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        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: 256000 },
+      },
+    },
+  },
+  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: {
+      "rekaai/reka-edge": {
+        id: "rekaai/reka-edge",
+        name: "Reka Edge",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-20",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 16384, output: 16384 },
+      },
+      "rekaai/reka-flash-3": {
+        id: "rekaai/reka-flash-3",
+        name: "Reka Flash 3",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2025-03-12",
+        last_updated: "2026-04-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.2 },
+        limit: { context: 65536, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "ibm-granite/granite-4.1-8b": {
+        id: "ibm-granite/granite-4.1-8b",
+        name: "IBM: Granite 4.1 8B",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-30",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.05, output: 0.1, cache_read: 0.05 },
+        limit: { context: 131072, output: 131072 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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": {
+        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-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 },
+      },
+      "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 },
+      },
+      "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": {
+        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-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-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-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 },
+      },
+      "deepseek/deepseek-v4-flash": {
+        id: "deepseek/deepseek-v4-flash",
+        name: "DeepSeek: DeepSeek V4 Flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.0028 },
+        limit: { context: 1048576, output: 384000 },
+      },
+      "deepseek/deepseek-v4-pro": {
+        id: "deepseek/deepseek-v4-pro",
+        name: "DeepSeek: DeepSeek V4 Pro",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.435, output: 0.87, cache_read: 0.003625 },
+        limit: { context: 1048576, output: 384000 },
+      },
+      "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-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-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-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 },
+      },
+      "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/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",
+      },
+      "openrouter/owl-alpha": {
+        id: "openrouter/owl-alpha",
+        name: "Owl Alpha",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 1048756, output: 262144 },
+        status: "alpha",
+      },
+      "openrouter/pareto-code": {
+        id: "openrouter/pareto-code",
+        name: "Pareto Code Router",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2026-04-21",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 200000, output: 65536 },
+      },
+      "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 },
+      },
+      "inclusionai/ling-2.6-1t:free": {
+        id: "inclusionai/ling-2.6-1t:free",
+        name: "inclusionAI: Ling-2.6-1T (free)",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-23",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 32768 },
+      },
+      "inclusionai/ling-2.6-flash": {
+        id: "inclusionai/ling-2.6-flash",
+        name: "inclusionAI: Ling-2.6 Flash",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-21",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.08, output: 0.24, cache_read: 0.016 },
+        limit: { context: 262144, output: 32768 },
+      },
+      "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/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-large-thinking": {
+        id: "arcee-ai/trinity-large-thinking",
+        name: "Arcee AI: Trinity Large Thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.22, output: 0.85 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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/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/trinity-large-preview": {
+        id: "arcee-ai/trinity-large-preview",
+        name: "Arcee AI: Trinity Large Preview",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-01-28",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.15, output: 0.45 },
+        limit: { context: 131000, output: 32768 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "~google/gemini-pro-latest": {
+        id: "~google/gemini-pro-latest",
+        name: "Google: Gemini Pro Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 12, cache_read: 0.2, cache_write: 0.375 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "~google/gemini-flash-latest": {
+        id: "~google/gemini-flash-latest",
+        name: "Google: Gemini Flash Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 3, cache_read: 0.05, cache_write: 0.08333333333333334 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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/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 },
+      },
+      "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/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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: "2025-08-26",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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/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-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/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-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-medium-3-5": {
+        id: "mistralai/mistral-medium-3-5",
+        name: "Mistral: Mistral Medium 3.5",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-30",
+        last_updated: "2026-05-07",
+        modalities: { input: ["image", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.5, output: 7.5 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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/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/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/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/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/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-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/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/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 },
+      },
+      "mistralai/mistral-small-2603": {
+        id: "mistralai/mistral-small-2603",
+        name: "Mistral: Mistral Small 4",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-16",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.6, cache_read: 0.015 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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/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/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": {
+        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-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/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-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/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/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 },
+      },
+      "~anthropic/claude-haiku-latest": {
+        id: "~anthropic/claude-haiku-latest",
+        name: "Anthropic: Claude Haiku Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-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 },
+      },
+      "~anthropic/claude-sonnet-latest": {
+        id: "~anthropic/claude-sonnet-latest",
+        name: "Anthropic: Claude Sonnet Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        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: 128000 },
+      },
+      "~anthropic/claude-opus-latest": {
+        id: "~anthropic/claude-opus-latest",
+        name: "Anthropic: Claude Opus Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-16",
+        last_updated: "2026-05-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: 1000000, output: 128000 },
+      },
+      "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-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-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-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.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-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.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.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.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-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-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-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 },
+      },
+      "x-ai/grok-4.20": {
+        id: "x-ai/grok-4.20",
+        name: "xAI: Grok 4.20",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-31",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "pdf", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6, cache_read: 0.2 },
+        limit: { context: 2000000, output: 2000000 },
+      },
+      "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.3": {
+        id: "x-ai/grok-4.3",
+        name: "xAI: Grok 4.3",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-05-01",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.25, output: 2.5, cache_read: 0.2 },
+        limit: { context: 1000000, output: 4096 },
+      },
+      "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-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-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 },
+      },
+      "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-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-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-4.20-multi-agent": {
+        id: "x-ai/grok-4.20-multi-agent",
+        name: "xAI: Grok 4.20 Multi-Agent",
+        attachment: true,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-31",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "pdf", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6, cache_read: 0.2 },
+        limit: { context: 2000000, output: 2000000 },
+      },
+      "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 },
+      },
+      "tencent/hy3-preview:free": {
+        id: "tencent/hy3-preview:free",
+        name: "Tencent: Hy3 Preview (free)",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-22",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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.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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "microsoft/phi-4-mini-instruct": {
+        id: "microsoft/phi-4-mini-instruct",
+        name: "Microsoft: Phi 4 Mini Instruct",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-10-17",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.08, output: 0.35, cache_read: 0.08 },
+        limit: { context: 128000, output: 128000 },
+      },
+      "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 },
+      },
+      "poolside/laguna-m.1:free": {
+        id: "poolside/laguna-m.1:free",
+        name: "Poolside: Laguna M.1 (free)",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "poolside/laguna-xs.2:free": {
+        id: "poolside/laguna-xs.2:free",
+        name: "Poolside: Laguna XS.2 (free)",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "cohere/command-r7b-12-2024": {
+        id: "cohere/command-r7b-12-2024",
+        name: "Cohere: Command R7B (12-2024)",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-3-super-120b-a12b": {
+        id: "nvidia/nemotron-3-super-120b-a12b",
+        name: "NVIDIA: Nemotron 3 Super",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-11",
+        last_updated: "2026-04-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.5, cache_read: 0.1 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free": {
+        id: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
+        name: "NVIDIA: Nemotron 3 Nano Omni (free)",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "audio", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "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 },
+      },
+      "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/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/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 },
+      },
+      "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 },
+      },
+      "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-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/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-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-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-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-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/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-chat-latest": {
+        id: "openai/gpt-chat-latest",
+        name: "OpenAI: GPT Chat Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        release_date: "2026-05-05",
+        last_updated: "2026-05-07",
+        modalities: { input: ["image", "pdf", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "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-5.5": {
+        id: "openai/gpt-5.5",
+        name: "OpenAI: GPT-5.5",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-24",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5 },
+        limit: { context: 1050000, output: 128000 },
+      },
+      "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-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-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-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/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.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/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/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/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/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.4-mini": {
+        id: "openai/gpt-5.4-mini",
+        name: "OpenAI: GPT-5.4 Mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-03-17",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "pdf", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "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/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-5.4-nano": {
+        id: "openai/gpt-5.4-nano",
+        name: "OpenAI: GPT-5.4 Nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-03-17",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "pdf", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "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-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.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-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-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-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/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.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-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/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/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-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/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.4-image-2": {
+        id: "openai/gpt-5.4-image-2",
+        name: "OpenAI: GPT-5.4 Image 2",
+        attachment: true,
+        reasoning: true,
+        tool_call: false,
+        temperature: false,
+        release_date: "2026-04-21",
+        last_updated: "2026-05-01",
+        modalities: { input: ["image", "text", "pdf"], output: ["image", "text"] },
+        open_weights: false,
+        cost: { input: 8, output: 15, cache_read: 2 },
+        limit: { context: 272000, output: 128000 },
+      },
+      "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-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-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.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-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/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 },
+      },
+      "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/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/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-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-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-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-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-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-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": {
+        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/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/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.5-pro": {
+        id: "openai/gpt-5.5-pro",
+        name: "OpenAI: GPT-5.5 Pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-24",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 30, output: 180 },
+        limit: { context: 1050000, 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/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/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-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-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 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "z-ai/glm-5v-turbo": {
+        id: "z-ai/glm-5v-turbo",
+        name: "Z.ai: GLM 5V Turbo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.2, output: 4, cache_read: 0.24 },
+        limit: { context: 202752, output: 131072 },
+      },
+      "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-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-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-5.1": {
+        id: "z-ai/glm-5.1",
+        name: "Z.ai: GLM 5.1",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.26, output: 3.96 },
+        limit: { context: 202752, output: 131072 },
+      },
+      "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.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-5-turbo": {
+        id: "z-ai/glm-5-turbo",
+        name: "Z.ai: GLM 5 Turbo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-15",
+        last_updated: "2026-04-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.2, output: 4, cache_read: 0.24 },
+        limit: { context: 202752, output: 131072 },
+      },
+      "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.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.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 },
+      },
+      "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 },
+      },
+      "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/qianfan-ocr-fast:free": {
+        id: "baidu/qianfan-ocr-fast:free",
+        name: "Baidu: Qianfan-OCR-Fast (free)",
+        attachment: true,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-05-01",
+        modalities: { input: ["image", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 65536, output: 28672 },
+      },
+      "baidu/cobuddy:free": {
+        id: "baidu/cobuddy:free",
+        name: "Baidu: CoBuddy (free)",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-05-06",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 131072, output: 65536 },
+      },
+      "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": {
+        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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "minimax/minimax-m2.7": {
+        id: "minimax/minimax-m2.7",
+        name: "MiniMax: MiniMax M2.7",
+        family: "minimax-m2.7",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        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-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-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-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": {
+        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 },
+      },
+      "~openai/gpt-latest": {
+        id: "~openai/gpt-latest",
+        name: "OpenAI: GPT Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5 },
+        limit: { context: 1050000, output: 128000 },
+      },
+      "~openai/gpt-mini-latest": {
+        id: "~openai/gpt-mini-latest",
+        name: "OpenAI: GPT Mini Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "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.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-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/qwen3.6-27b": {
+        id: "qwen/qwen3.6-27b",
+        name: "Qwen: Qwen3.6 27B",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.325, output: 3.25 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "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/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-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.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-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/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/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/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/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.6-35b-a3b": {
+        id: "qwen/qwen3.6-35b-a3b",
+        name: "Qwen: Qwen3.6 35B A3B",
+        attachment: true,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.1612, output: 0.96525, cache_read: 0.1612 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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-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-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-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 },
+      },
+      "qwen/qwen3.6-plus": {
+        id: "qwen/qwen3.6-plus",
+        name: "Qwen: Qwen3.6 Plus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-08-26",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.325, output: 1.95, cache_read: 0.0325, cache_write: 0.40625 },
+        limit: { context: 1000000, 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/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-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/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-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-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/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-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-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-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-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-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.5-plus-20260420": {
+        id: "qwen/qwen3.5-plus-20260420",
+        name: "Qwen: Qwen3.5 Plus 2026-04-20",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 2.4 },
+        limit: { context: 1000000, output: 65536 },
+      },
+      "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-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-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-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-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/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-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/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.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/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/qwen3.6-max-preview": {
+        id: "qwen/qwen3.6-max-preview",
+        name: "Qwen: Qwen3.6 Max Preview",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.04, output: 6.24, cache_write: 1.3 },
+        limit: { context: 262144, 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/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-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/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.6-flash": {
+        id: "qwen/qwen3.6-flash",
+        name: "Qwen: Qwen3.6 Flash",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 1.5, cache_write: 0.3125 },
+        limit: { context: 1000000, output: 65536 },
+      },
+      "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 },
+      },
+      "kwaipilot/kat-coder-pro-v2": {
+        id: "kwaipilot/kat-coder-pro-v2",
+        name: "Kwaipilot: KAT-Coder-Pro V2",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-04-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        limit: { context: 256000, output: 80000 },
+      },
+      "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/lyria-3-clip-preview": {
+        id: "google/lyria-3-clip-preview",
+        name: "Google: Lyria 3 Clip Preview",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-30",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text"], output: ["audio", "text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        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-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-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/lyria-3-pro-preview": {
+        id: "google/lyria-3-pro-preview",
+        name: "Google: Lyria 3 Pro Preview",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-30",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text"], output: ["audio", "text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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.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.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-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/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 },
+      },
+      "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-4-31b-it": {
+        id: "google/gemma-4-31b-it",
+        name: "Google: Gemma 4 31B",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.4 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "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/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/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-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/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/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/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/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/gemma-4-26b-a4b-it": {
+        id: "google/gemma-4-26b-a4b-it",
+        name: "Google: Gemma 4 26B A4B",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-03",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.12, output: 0.4 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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-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 },
+      },
+      "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-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.6": {
+        id: "moonshotai/kimi-k2.6",
+        name: "MoonshotAI: Kimi K2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_details" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "~moonshotai/kimi-latest": {
+        id: "~moonshotai/kimi-latest",
+        name: "MoonshotAI: Kimi Latest",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-27",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.74, output: 3.49, cache_read: 0.14 },
+        limit: { context: 262142, output: 262142 },
+      },
+      "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/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/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 },
+      },
+      "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 },
+      },
+      "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.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.6-fast": {
+        id: "anthropic/claude-opus-4.6-fast",
+        name: "Anthropic: Claude Opus 4.6 (Fast)",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-05-31",
+        release_date: "2026-04-07",
+        last_updated: "2026-04-11",
+        modalities: { input: ["image", "text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 30, output: 150, cache_read: 3, cache_write: 37.5 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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.6": {
+        id: "anthropic/claude-opus-4.6",
+        name: "Anthropic: Claude Opus 4.6",
+        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"], 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-opus-4.7": {
+        id: "anthropic/claude-opus-4.7",
+        name: "Anthropic: Claude Opus 4.7",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-16",
+        last_updated: "2026-05-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: 1000000, output: 128000 },
+      },
+      "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.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-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-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-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-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-sonnet-4.6": {
+        id: "anthropic/claude-sonnet-4.6",
+        name: "Anthropic: Claude Sonnet 4.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "xiaomi/mimo-v2.5-pro": {
+        id: "xiaomi/mimo-v2.5-pro",
+        name: "Xiaomi: MiMo V2.5 Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "xiaomi/mimo-v2-omni": {
+        id: "xiaomi/mimo-v2-omni",
+        name: "Xiaomi: MiMo-V2-Omni",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 2, cache_read: 0.08 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "xiaomi/mimo-v2.5": {
+        id: "xiaomi/mimo-v2.5",
+        name: "Xiaomi: MiMo-V2.5",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: {
+          input: 0.4,
+          output: 2,
+          cache_read: 0.08,
+          context_over_200k: { input: 0.8, output: 4, cache_read: 0.16 },
+        },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "xiaomi/mimo-v2-pro": {
+        id: "xiaomi/mimo-v2-pro",
+        name: "Xiaomi: MiMo-V2-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "xiaomi/mimo-v2-flash": {
+        id: "xiaomi/mimo-v2-flash",
+        name: "Xiaomi: MiMo-V2-Flash",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        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.09, output: 0.29, cache_read: 0.045 },
+        limit: { context: 262144, output: 65536 },
+      },
+    },
+  },
+  "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.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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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, 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 },
+      },
+      "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-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 },
+      },
+      "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-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-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-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 },
+      },
+      "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 },
+      },
+      "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-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-05",
+        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 },
+      },
+      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-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-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-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-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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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, 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 },
+      },
+      "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.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 },
+      },
+    },
+  },
+  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 },
+      },
+    },
+  },
+  "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/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/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/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/zai-org/glm-4.7-flash": {
+        id: "workers-ai/@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 },
+      },
+      "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/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/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/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 },
+      },
+      "workers-ai/@cf/nvidia/nemotron-3-120b-a12b": {
+        id: "workers-ai/@cf/nvidia/nemotron-3-120b-a12b",
+        name: "Nemotron 3 Super 120B",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 1.5 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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/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/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/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/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-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-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/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.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.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.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.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.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-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-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/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/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/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/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/moonshotai/kimi-k2.5": {
+        id: "workers-ai/@cf/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"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3, cache_read: 0.1 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "workers-ai/@cf/moonshotai/kimi-k2.6": {
+        id: "workers-ai/@cf/moonshotai/kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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/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/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-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/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/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/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 },
+      },
+      "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-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-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-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/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.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/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/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-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/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-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/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-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.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 },
+      },
+      "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-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-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-opus-4-7": {
+        id: "anthropic/claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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" },
+      },
+      "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-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-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-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-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-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": {
+        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-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-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,
+          context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 },
+        },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "openai/gpt-5.5": {
+        id: "openai/gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+      },
+    },
+  },
+  "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.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: 400000, input: 128000, output: 128000 },
+        status: "deprecated",
+      },
+      "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"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 144000, input: 128000, output: 64000 },
+      },
+      "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, input: 128000, output: 64000 },
+      },
+      "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, input: 128000, output: 64000 },
+      },
+      "gpt-5.5": {
+        id: "gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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: 264000, input: 128000, 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", "audio", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 128000, input: 128000, output: 64000 },
+        status: "deprecated",
+      },
+      "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 },
+      },
+      "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, input: 128000, 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: 0, output: 0 },
+        limit: { context: 264000, input: 128000, output: 64000 },
+      },
+      "gpt-5.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "GPT-5.4 Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "claude-opus-4.7": {
+        id: "claude-opus-4.7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 144000, input: 128000, output: 64000 },
+      },
+      "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 },
+      },
+      "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: 400000, input: 128000, output: 128000 },
+        status: "deprecated",
+      },
+      "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: 216000, input: 128000, output: 16000 },
+        status: "deprecated",
+      },
+      "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, input: 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: 264000, input: 128000, output: 64000 },
+        status: "deprecated",
+      },
+      "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: 144000, input: 128000, output: 32000 },
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "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: 160000, input: 128000, output: 32000 },
+      },
+      "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-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: 128000, input: 64000, output: 4096 },
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "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: 144000, input: 128000, output: 32000 },
+      },
+      "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: 128000, input: 64000, 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-31",
+        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: 200000, input: 128000, output: 32000 },
+      },
+      "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: 400000, input: 128000, output: 128000 },
+        status: "deprecated",
+      },
+    },
+  },
+  mixlayer: {
+    id: "mixlayer",
+    env: ["MIXLAYER_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://models.mixlayer.ai/v1",
+    name: "Mixlayer",
+    doc: "https://docs.mixlayer.com",
+    models: {
+      "qwen/qwen3.5-122b-a10b": {
+        id: "qwen/qwen3.5-122b-a10b",
+        name: "Qwen3.5 122B A10B",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.4, output: 3.2 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "qwen/qwen3.5-27b": {
+        id: "qwen/qwen3.5-27b",
+        name: "Qwen3.5 27B",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 2.4 },
+        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-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3.6 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "qwen/qwen3.5-9b": {
+        id: "qwen/qwen3.5-9b",
+        name: "Qwen3.5 9B",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.4 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "qwen/qwen3.5-35b-a3b": {
+        id: "qwen/qwen3.5-35b-a3b",
+        name: "Qwen3.5 35B A3B",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 1.3 },
+        limit: { context: 262144, output: 262144 },
+      },
+    },
+  },
+  "xiaomi-token-plan-sgp": {
+    id: "xiaomi-token-plan-sgp",
+    env: ["XIAOMI_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://token-plan-sgp.xiaomimimo.com/v1",
+    name: "Xiaomi Token Plan (Singapore)",
+    doc: "https://platform.xiaomimimo.com/#/docs",
+    models: {
+      "mimo-v2-tts": {
+        id: "mimo-v2-tts",
+        name: "MiMo-V2-TTS",
+        family: "mimo",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["audio"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 8192, output: 16384 },
+      },
+      "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, output: 0, cache_read: 0 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "mimo-v2-pro": {
+        id: "mimo-v2-pro",
+        name: "MiMo-V2-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "mimo-v2.5": {
+        id: "mimo-v2.5",
+        name: "MiMo-V2.5",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 0, output: 0, cache_read: 0 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "mimo-v2-omni": {
+        id: "mimo-v2-omni",
+        name: "MiMo-V2-Omni",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "mimo-v2.5-pro": {
+        id: "mimo-v2.5-pro",
+        name: "MiMo-V2.5-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 0, output: 0, cache_read: 0 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+    },
+  },
+  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-5v-turbo": {
+        id: "glm-5v-turbo",
+        name: "GLM-5V-Turbo",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.2, output: 4, cache_read: 0.24, cache_write: 0 },
+        limit: { context: 200000, 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-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.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-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26, cache_write: 0 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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 },
+      },
+      "glm-5-turbo": {
+        id: "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-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.2, output: 4, cache_read: 0.24, cache_write: 0 },
+        limit: { context: 200000, 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.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.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.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 },
+      },
+    },
+  },
+  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: {
+      "minimax-m2.7": {
+        id: "minimax-m2.7",
+        name: "MiniMax M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        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" },
+      },
+      "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" },
+      },
+      "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 },
+      },
+      "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 },
+        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 },
+      },
+      "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.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" },
+      },
+      "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-08-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" },
+      },
+      "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-7": {
+        id: "claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/openai" },
+      },
+      "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" },
+      },
+      "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" },
+      },
+      "ring-2.6-1t-free": {
+        id: "ring-2.6-1t-free",
+        name: "Ring 2.6 1T Free",
+        family: "ring-1t-free",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2025-06",
+        release_date: "2026-05-08",
+        last_updated: "2026-05-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262000, output: 66000 },
+      },
+      "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" },
+      },
+      "big-pickle": {
+        id: "big-pickle",
+        name: "Big Pickle",
+        family: "big-pickle",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: 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 },
+      },
+      "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" },
+      },
+      "qwen3.6-plus": {
+        id: "qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen3.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 3, cache_read: 0.05, cache_write: 0.625 },
+        limit: { context: 262144, output: 65536 },
+        provider: { npm: "@ai-sdk/anthropic" },
+      },
+      "gpt-5.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "GPT-5.4 Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai" },
+      },
+      "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 },
+        status: "deprecated",
+        provider: { npm: "@ai-sdk/anthropic" },
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "gpt-5.4-nano": {
+        id: "gpt-5.4-nano",
+        name: "GPT-5.4 Nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai" },
+      },
+      "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" },
+      },
+      "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" },
+      },
+      "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" },
+      },
+      "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" },
+      },
+      "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",
+      },
+      "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.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" },
+      },
+      "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",
+      },
+      "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" },
+      },
+      "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" },
+      },
+      "qwen3.6-plus-free": {
+        id: "qwen3.6-plus-free",
+        name: "Qwen3.6 Plus Free",
+        family: "qwen-free",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-30",
+        last_updated: "2026-03-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 1048576, output: 64000 },
+        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 },
+        status: "deprecated",
+      },
+      "ling-2.6-flash-free": {
+        id: "ling-2.6-flash-free",
+        name: "Ling 2.6 Flash Free",
+        family: "ling-flash-free",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-06",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262100, output: 32800 },
+        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 },
+        status: "deprecated",
+        provider: { npm: "@ai-sdk/google" },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-10",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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" },
+      },
+      "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",
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "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" },
+      },
+      "hy3-preview-free": {
+        id: "hy3-preview-free",
+        name: "Hy3 preview Free",
+        family: "hy3-free",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-06",
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 256000, output: 64000 },
+        status: "deprecated",
+      },
+      "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 },
+      },
+      "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",
+      },
+      "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" },
+      },
+      "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",
+      },
+      "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" },
+      },
+      "qwen3.5-plus": {
+        id: "qwen3.5-plus",
+        name: "Qwen3.5 Plus",
+        family: "qwen3.5",
+        attachment: true,
+        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.2, output: 1.2, cache_read: 0.02, cache_write: 0.25 },
+        limit: { context: 262144, output: 65536 },
+        provider: { npm: "@ai-sdk/anthropic" },
+      },
+      "mimo-v2-pro-free": {
+        id: "mimo-v2-pro-free",
+        name: "MiMo V2 Pro Free",
+        family: "mimo-pro-free",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 1048576, output: 64000 },
+        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: 204800, output: 128000 },
+      },
+      "gpt-5.5-pro": {
+        id: "gpt-5.5-pro",
+        name: "GPT-5.5 Pro",
+        family: "gpt-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: false,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        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" },
+      },
+      "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.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" },
+      },
+      "mimo-v2-omni-free": {
+        id: "mimo-v2-omni-free",
+        name: "MiMo V2 Omni Free",
+        family: "mimo-omni-free",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "pdf"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 262144, output: 64000 },
+        status: "deprecated",
+      },
+      "gpt-5.5": {
+        id: "gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+        provider: { npm: "@ai-sdk/openai" },
+      },
+      "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 },
+        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-05-31",
+        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 },
+        provider: { npm: "@ai-sdk/anthropic" },
+      },
+    },
+  },
+  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-2603": {
+        id: "step-3.5-flash-2603",
+        name: "Step 3.5 Flash 2603",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.3, cache_read: 0.02 },
+        limit: { context: 256000, input: 256000, output: 256000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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.5-397B-A17B": {
+        id: "Qwen/Qwen3.5-397B-A17B",
+        name: "Qwen3.5-397B-A17B",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-07",
+        release_date: "2025-07-15",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3.6, cache_read: 0.06, cache_write: 0.75 },
+        limit: { context: 262144, input: 250000, 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-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-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 },
+      },
+      "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-fast": {
+        id: "Qwen/Qwen3-235B-A22B-Thinking-2507-fast",
+        name: "Qwen3-235B-A22B-Thinking-2507-fast",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-07",
+        release_date: "2025-07-25",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 2, cache_read: 0.05, cache_write: 0.625 },
+        limit: { context: 8000, input: 7000, output: 8192 },
+      },
+      "Qwen/Qwen3.5-397B-A17B-fast": {
+        id: "Qwen/Qwen3.5-397B-A17B-fast",
+        name: "Qwen3.5-397B-A17B-fast",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-07",
+        release_date: "2025-07-15",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3.6, cache_read: 0.06, cache_write: 0.75 },
+        limit: { context: 8000, input: 7000, output: 8192 },
+      },
+      "Qwen/Qwen3-Next-80B-A3B-Thinking-fast": {
+        id: "Qwen/Qwen3-Next-80B-A3B-Thinking-fast",
+        name: "Qwen3-Next-80B-A3B-Thinking-fast",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-07",
+        release_date: "2025-07-25",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 1.2, cache_read: 0.015, cache_write: 0.1875 },
+        limit: { context: 8000, input: 7000, 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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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/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 },
+      },
+      "nvidia/Nemotron-3-Nano-Omni": {
+        id: "nvidia/Nemotron-3-Nano-Omni",
+        name: "Nemotron-3-Nano-Omni",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2025-01-20",
+        last_updated: "2026-05-07",
+        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: 65536, input: 60000, output: 8192 },
+      },
+      "deepseek-ai/DeepSeek-V3.2-fast": {
+        id: "deepseek-ai/DeepSeek-V3.2-fast",
+        name: "DeepSeek-V3.2-fast",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2025-01-27",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.4, output: 2, cache_read: 0.04, cache_write: 0.5 },
+        limit: { context: 8000, input: 7000, output: 8192 },
+      },
+      "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 },
+      },
+      "openai/gpt-oss-120b-fast": {
+        id: "openai/gpt-oss-120b-fast",
+        name: "gpt-oss-120b-fast",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-06",
+        release_date: "2025-06-10",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.5, cache_read: 0.01, cache_write: 0.125 },
+        limit: { context: 8000, input: 7000, 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 },
+      },
+      "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": {
+        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 },
+      },
+      "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 },
+      },
+      "MiniMaxAI/MiniMax-M2.5": {
+        id: "MiniMaxAI/MiniMax-M2.5",
+        name: "MiniMax-M2.5",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2025-01-20",
+        last_updated: "2026-05-07",
+        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: 196608, input: 190000, output: 8192 },
+      },
+      "MiniMaxAI/MiniMax-M2.5-fast": {
+        id: "MiniMaxAI/MiniMax-M2.5-fast",
+        name: "MiniMax-M2.5-fast",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2025-01-20",
+        last_updated: "2026-05-07",
+        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: 8000, input: 7000, output: 8192 },
+      },
+      "deepseek-ai/DeepSeek-V4-Pro": {
+        id: "deepseek-ai/DeepSeek-V4-Pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.75, output: 3.5, cache_read: 0.15 },
+        limit: { context: 1000000, output: 384000 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "novita/kimi-k2.5": {
+        id: "novita/kimi-k2.5",
+        name: "Kimi-K2.5",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-01-27",
+        last_updated: "2026-01-27",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.6, output: 3, cache_read: 0.1 },
+        limit: { context: 128000, 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 },
+        status: "deprecated",
+      },
+      "novita/glm-5": {
+        id: "novita/glm-5",
+        name: "GLM-5",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-02-15",
+        last_updated: "2026-02-15",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 3.2, cache_read: 0.2 },
+        limit: { context: 205000, output: 131072 },
+      },
+      "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/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/kimi-k2.6": {
+        id: "novita/kimi-k2.6",
+        name: "Kimi-K2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-20",
+        last_updated: "2026-05-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.96, output: 4.04, cache_read: 0.16 },
+        limit: { context: 262144, input: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "novita/deepseek-v3.2": {
+        id: "novita/deepseek-v3.2",
+        name: "DeepSeek-V3.2",
+        attachment: true,
+        reasoning: true,
+        tool_call: 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.13 },
+        limit: { context: 128000, 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/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 },
+      },
+      "fireworks-ai/kimi-k2.5-fw": {
+        id: "fireworks-ai/kimi-k2.5-fw",
+        name: "Kimi-K2.5-FW",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-01-27",
+        last_updated: "2026-01-27",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, input: 245760, output: 16384 },
+      },
+      "empiriolabs/deepseek-v4-pro-el": {
+        id: "empiriolabs/deepseek-v4-pro-el",
+        name: "DeepSeek-V4-Pro-EL",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-05-02",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.67, output: 3.33 },
+        limit: { context: 1000000, input: 1000000, output: 384000 },
+      },
+      "empiriolabs/deepseek-v4-flash-el": {
+        id: "empiriolabs/deepseek-v4-flash-el",
+        name: "DeepSeek-V4-Flash-EL",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-05-02",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28 },
+        limit: { context: 1000000, input: 1000000, output: 384000 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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,
+        cost: { input: 0.35, output: 0.75 },
+        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,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 128000, 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 },
+        status: "deprecated",
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "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 },
+      },
+      "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-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-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-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-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.20-multi-agent": {
+        id: "xai/grok-4.20-multi-agent",
+        name: "Grok-4.20-Multi-Agent",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-03-13",
+        last_updated: "2026-03-13",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6, cache_read: 0.2 },
+        limit: { context: 128000, output: 0 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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/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/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 },
+        status: "deprecated",
+      },
+      "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/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/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/gpt-image-2": {
+        id: "openai/gpt-image-2",
+        name: "GPT-Image-2",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["image"] },
+        open_weights: false,
+        cost: { input: 5.0505, output: 32.3232, cache_read: 1.2626 },
+        limit: { context: 0, output: 0 },
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "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-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-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-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-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/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/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-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/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.4-mini": {
+        id: "openai/gpt-5.4-mini",
+        name: "GPT-5.4-Mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-03-12",
+        last_updated: "2026-03-12",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.68, output: 4, cache_read: 0.068 },
+        limit: { context: 400000, input: 272000, 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/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-5.4-nano": {
+        id: "openai/gpt-5.4-nano",
+        name: "GPT-5.4-Nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.18, output: 1.1, cache_read: 0.018 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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/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/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.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/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/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.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/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/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/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/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-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-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.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.3-codex-spark": {
+        id: "openai/gpt-5.3-codex-spark",
+        name: "GPT-5.3-Codex-Spark",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-03-04",
+        last_updated: "2026-03-04",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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-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/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-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/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-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": {
+        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/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/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-image-1.5": {
+        id: "openai/gpt-image-1.5",
+        name: "gpt-image-1.5",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        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-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-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-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 },
+      },
+      "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-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 },
+        status: "deprecated",
+      },
+      "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/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 },
+      },
+      "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/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-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/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/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/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 },
+        status: "deprecated",
+      },
+      "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/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/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/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/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-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/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/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/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/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/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 },
+        status: "deprecated",
+      },
+      "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 },
+      },
+      "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/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/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/gemma-4-31b": {
+        id: "google/gemma-4-31b",
+        name: "Gemma-4-31B",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 8192 },
+      },
+      "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/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 },
+      },
+      "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 },
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "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-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-opus-4.7": {
+        id: "anthropic/claude-opus-4.7",
+        name: "Claude-Opus-4.7",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-15",
+        last_updated: "2026-04-15",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 4.3, output: 21, cache_read: 0.43, cache_write: 5.4 },
+        limit: { context: 1048576, output: 128000 },
+      },
+      "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.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-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 },
+      },
+      "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-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-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-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 },
+        status: "deprecated",
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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.5": {
+        id: "openai/gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text", "image"], output: ["text", "image"] },
+        open_weights: false,
+        cost: { input: 4.5455, output: 27.2727, cache_read: 0.4545 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "openai/gpt-5.5-pro": {
+        id: "openai/gpt-5.5-pro",
+        name: "GPT-5.5-Pro",
+        family: "gpt-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: false,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text", "image"], output: ["text", "image"] },
+        open_weights: false,
+        cost: { input: 27.2727, output: 163.6364 },
+        limit: { context: 400000, output: 128000 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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": {
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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": {
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "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: {
+      "minimax-m2.7": {
+        id: "minimax-m2.7",
+        name: "minimax-m2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 196608, output: 196608 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "gemma4:31b": {
+        id: "gemma4:31b",
+        name: "gemma4:31b",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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: 65536 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "gemini-3-flash-preview": {
+        id: "gemini-3-flash-preview",
+        name: "gemini-3-flash-preview",
+        family: "gemini-flash",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        knowledge: "2025-01",
+        release_date: "2025-12-17",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "glm-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        release_date: "2026-03-27",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 202752, output: 131072 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "kimi-k2.6:cloud": {
+        id: "kimi-k2.6:cloud",
+        name: "kimi-k2.6:cloud",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "deepseek-v4-flash": {
+        id: "deepseek-v4-flash",
+        name: "deepseek-v4-flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 1048576, output: 1048576 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "deepseek-v4-pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 1048576, output: 1048576 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "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-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-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, 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-5-turbo": {
+        id: "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-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "glm-5v-turbo": {
+        id: "glm-5v-turbo",
+        name: "GLM-5V-Turbo",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 200000, output: 131072 },
+      },
+    },
+  },
+  "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: {
+      "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,
+        structured_output: 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 },
+      },
+      "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 },
+      },
+      "nvidia.nemotron-super-3-120b": {
+        id: "nvidia.nemotron-super-3-120b",
+        name: "NVIDIA Nemotron 3 Super 120B A12B",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.65 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "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 },
+      },
+      "mistral.ministral-3-8b-instruct": {
+        id: "mistral.ministral-3-8b-instruct",
+        name: "Ministral 3 8B",
+        family: "ministral",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: 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 },
+      },
+      "au.anthropic.claude-opus-4-6-v1": {
+        id: "au.anthropic.claude-opus-4-6-v1",
+        name: "AU Anthropic Claude Opus 4.6",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: 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: 16.5, output: 82.5, cache_read: 1.65, cache_write: 20.625 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "mistral.ministral-3-3b-instruct": {
+        id: "mistral.ministral-3-3b-instruct",
+        name: "Ministral 3 3B",
+        family: "ministral",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: 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 },
+      },
+      "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,
+        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 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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 },
+      },
+      "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,
+        structured_output: 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.voxtral-small-24b-2507": {
+        id: "mistral.voxtral-small-24b-2507",
+        name: "Voxtral Small 24B 2507",
+        family: "mistral",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: true,
+        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 },
+      },
+      "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 },
+      },
+      "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,
+        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 },
+      },
+      "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 },
+      },
+      "global.anthropic.claude-opus-4-7": {
+        id: "global.anthropic.claude-opus-4-7",
+        name: "Claude Opus 4.7 (Global)",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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,
+        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 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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,
+        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 },
+      },
+      "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 },
+      },
+      "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,
+        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 },
+        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,
+        structured_output: 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 },
+      },
+      "zai.glm-5": {
+        id: "zai.glm-5",
+        name: "GLM-5",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3.2 },
+        limit: { context: 202752, output: 101376 },
+      },
+      "qwen.qwen3-32b-v1:0": {
+        id: "qwen.qwen3-32b-v1:0",
+        name: "Qwen3 32B (dense)",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: 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 },
+      },
+      "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 },
+      },
+      "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,
+        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 },
+      },
+      "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 },
+      },
+      "us.anthropic.claude-opus-4-7": {
+        id: "us.anthropic.claude-opus-4-7",
+        name: "Claude Opus 4.7 (US)",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+      "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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "anthropic.claude-opus-4-7": {
+        id: "anthropic.claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+      "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-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 },
+      },
+      "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,
+        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 },
+      },
+      "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 },
+      },
+      "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-v1:0": {
+        id: "deepseek.v3-v1:0",
+        name: "DeepSeek-V3.1",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: 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.magistral-small-2509": {
+        id: "mistral.magistral-small-2509",
+        name: "Magistral Small 1.2",
+        family: "magistral",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: 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 },
+      },
+      "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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: 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-14b-instruct": {
+        id: "mistral.ministral-3-14b-instruct",
+        name: "Ministral 14B 3.0",
+        family: "ministral",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: 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 },
+      },
+      "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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "eu.anthropic.claude-opus-4-7": {
+        id: "eu.anthropic.claude-opus-4-7",
+        name: "Claude Opus 4.7 (EU)",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+      "minimax.minimax-m2.5": {
+        id: "minimax.minimax-m2.5",
+        name: "MiniMax M2.5",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: false,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2 },
+        limit: { context: 196608, output: 98304 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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,
+        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 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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 },
+      },
+      "au.anthropic.claude-sonnet-4-6": {
+        id: "au.anthropic.claude-sonnet-4-6",
+        name: "AU Anthropic Claude Sonnet 4.6",
+        family: "claude-sonnet",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: 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.3, output: 16.5, cache_read: 0.33, cache_write: 4.125 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "qwen.qwen3-coder-next": {
+        id: "qwen.qwen3-coder-next",
+        name: "Qwen3 Coder Next",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-06",
+        last_updated: "2026-02-06",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.22, output: 1.8 },
+        limit: { context: 131072, output: 65536 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "us.deepseek.r1-v1:0": {
+        id: "us.deepseek.r1-v1:0",
+        name: "DeepSeek-R1 (US)",
+        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 },
+      },
+      "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,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05-31",
+        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 },
+      },
+      "us.meta.llama4-maverick-17b-instruct-v1:0": {
+        id: "us.meta.llama4-maverick-17b-instruct-v1:0",
+        name: "Llama 4 Maverick 17B Instruct (US)",
+        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 },
+      },
+      "au.anthropic.claude-haiku-4-5-20251001-v1:0": {
+        id: "au.anthropic.claude-haiku-4-5-20251001-v1:0",
+        name: "Claude Haiku 4.5 (AU)",
+        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 },
+      },
+      "jp.anthropic.claude-sonnet-4-5-20250929-v1:0": {
+        id: "jp.anthropic.claude-sonnet-4-5-20250929-v1:0",
+        name: "Claude Sonnet 4.5 (JP)",
+        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-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-31",
+        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 },
+      },
+      "jp.anthropic.claude-sonnet-4-6": {
+        id: "jp.anthropic.claude-sonnet-4-6",
+        name: "Claude Sonnet 4.6 (JP)",
+        family: "claude-sonnet",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08-31",
+        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 },
+      },
+      "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-31",
+        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 },
+      },
+      "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-31",
+        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 },
+      },
+      "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,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05-31",
+        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 },
+      },
+      "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,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05-31",
+        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 },
+      },
+      "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 },
+      },
+      "au.anthropic.claude-sonnet-4-5-20250929-v1:0": {
+        id: "au.anthropic.claude-sonnet-4-5-20250929-v1:0",
+        name: "Claude Sonnet 4.5 (AU)",
+        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 },
+      },
+      "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-31",
+        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 },
+      },
+      "us.meta.llama4-scout-17b-instruct-v1:0": {
+        id: "us.meta.llama4-scout-17b-instruct-v1:0",
+        name: "Llama 4 Scout 17B Instruct (US)",
+        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 },
+      },
+      "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,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05-31",
+        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 },
+      },
+      "jp.anthropic.claude-opus-4-7": {
+        id: "jp.anthropic.claude-opus-4-7",
+        name: "Claude Opus 4.7 (JP)",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+    },
+  },
+  "the-grid-ai": {
+    id: "the-grid-ai",
+    env: ["THEGRIDAI_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.thegrid.ai/v1",
+    name: "The Grid AI",
+    doc: "https://thegrid.ai/docs",
+    models: {
+      "text-prime": {
+        id: "text-prime",
+        name: "Text Prime",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-26",
+        last_updated: "2026-02-26",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        limit: { context: 128000, output: 30000 },
+        status: "beta",
+      },
+      "text-standard": {
+        id: "text-standard",
+        name: "Text Standard",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-26",
+        last_updated: "2026-02-26",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        limit: { context: 128000, output: 16000 },
+        status: "beta",
+      },
+      "text-max": {
+        id: "text-max",
+        name: "Text Max",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-24",
+        last_updated: "2026-03-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        limit: { context: 1000000, output: 128000 },
+        status: "beta",
+      },
+    },
+  },
+  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.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 },
+      },
+      "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 },
+      },
+      "nvidia/Nemotron-120B-A12B": {
+        id: "nvidia/Nemotron-120B-A12B",
+        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 },
+      },
+      "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-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 },
+      },
+      "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",
+      },
+      "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 },
+      },
+      "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",
+      },
+      "moonshotai/Kimi-K2.6": {
+        id: "moonshotai/Kimi-K2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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-V4-Pro": {
+        id: "deepseek-ai/DeepSeek-V4-Pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.15 },
+        limit: { context: 1000000, output: 384000 },
+      },
+    },
+  },
+  frogbot: {
+    id: "frogbot",
+    env: ["FROGBOT_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://app.frogbot.ai/api/v1",
+    name: "FrogBot",
+    doc: "https://docs.frogbot.ai",
+    models: {
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-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: 200000, output: 64000 },
+      },
+      "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 },
+      },
+      "claude-opus-4-7": {
+        id: "claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+      "zai-glm-5-1": {
+        id: "zai-glm-5-1",
+        name: "Z.AI GLM-5.1",
+        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: true,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        limit: { context: 198000, output: 8192 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "gpt-5-4-nano": {
+        id: "gpt-5-4-nano",
+        name: "GPT-5.4 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.2, output: 1.25, cache_read: 0.02 },
+        limit: { context: 400000, 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,
+        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-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 },
+      },
+      "gpt-5-5": {
+        id: "gpt-5-5",
+        name: "GPT-5.5",
+        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 },
+      },
+      "grok-4-3": {
+        id: "grok-4-3",
+        name: "Grok 4.3",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2024-11",
+        release_date: "2026-04-30",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.25, output: 2.5, cache_read: 0.2 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "gpt-5-4-mini": {
+        id: "gpt-5-4-mini",
+        name: "GPT-5.4 Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        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-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 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek v4 Pro",
+        family: "deepseek",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2026-01",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.14 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "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 },
+      },
+      "qwen-3-6-plus": {
+        id: "qwen-3-6-plus",
+        name: "Qwen 3.6 Plus",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 3, cache_read: 0.1 },
+        limit: { context: 1000000, output: 64000 },
+      },
+      "minimax-m2-7": {
+        id: "minimax-m2-7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-09",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        limit: { context: 192000, output: 8192 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "kimi-k2-6": {
+        id: "kimi-k2-6",
+        name: "Kimi-K2.6",
+        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.95, output: 4, cache_read: 0.16 },
+        limit: { context: 256000, output: 128000 },
+      },
+      "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 },
+      },
+    },
+  },
+  "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-5v-turbo": {
+        id: "glm-5v-turbo",
+        name: "GLM-5V-Turbo",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "glm-5-turbo": {
+        id: "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-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, 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.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 },
+      },
+    },
+  },
+  "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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.6-plus": {
+        id: "qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  venice: {
+    id: "venice",
+    env: ["VENICE_API_KEY"],
+    npm: "venice-ai-sdk-provider",
+    name: "Venice AI",
+    doc: "https://docs.venice.ai",
+    models: {
+      "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 },
+      },
+      "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 },
+      },
+      "grok-4-20-multi-agent": {
+        id: "grok-4-20-multi-agent",
+        name: "Grok 4.20 Multi-Agent",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-12",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 1.42,
+          output: 2.83,
+          cache_read: 0.23,
+          context_over_200k: { input: 2.83, output: 5.67, cache_read: 0.45 },
+        },
+        limit: { context: 2000000, output: 128000 },
+      },
+      "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 },
+      },
+      "z-ai-glm-5v-turbo": {
+        id: "z-ai-glm-5v-turbo",
+        name: "GLM 5V Turbo",
+        family: "glmv",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.5, output: 5, cache_read: 0.3 },
+        limit: { context: 200000, output: 32768 },
+      },
+      "gemma-4-uncensored": {
+        id: "gemma-4-uncensored",
+        name: "Gemma 4 Uncensored",
+        family: "gemma",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-13",
+        last_updated: "2026-04-19",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1625, output: 0.5 },
+        limit: { context: 256000, output: 8192 },
+      },
+      "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-04-09",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.23, output: 0.57, cache_read: 0.06 },
+        limit: { context: 1000000, output: 30000 },
+      },
+      "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,
+        knowledge: "2025-08-31",
+        release_date: "2026-02-17",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3.6, output: 18, cache_read: 0.36, cache_write: 4.5 },
+        limit: { context: 1000000, output: 64000 },
+      },
+      "nvidia-nemotron-cascade-2-30b-a3b": {
+        id: "nvidia-nemotron-cascade-2-30b-a3b",
+        name: "Nemotron Cascade 2 30B A3B",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-24",
+        last_updated: "2026-04-09",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.8 },
+        limit: { context: 256000, output: 32768 },
+      },
+      "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": {
+        id: "grok-4-20",
+        name: "Grok 4.20",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-12",
+        last_updated: "2026-05-07",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 1.42,
+          output: 2.83,
+          cache_read: 0.23,
+          context_over_200k: { input: 2.83, output: 5.67, cache_read: 0.45 },
+        },
+        limit: { context: 2000000, output: 128000 },
+      },
+      "google-gemma-4-26b-a4b-it": {
+        id: "google-gemma-4-26b-a4b-it",
+        name: "Google Gemma 4 26B A4B Instruct",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1625, output: 0.5 },
+        limit: { context: 256000, output: 8192 },
+      },
+      "claude-opus-4-7": {
+        id: "claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 6, output: 30, cache_read: 0.6, cache_write: 7.5 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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 },
+      },
+      "qwen3-5-397b-a17b": {
+        id: "qwen3-5-397b-a17b",
+        name: "Qwen 3.5 397B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-16",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.75, output: 4.5 },
+        limit: { context: 128000, output: 32768 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "venice-uncensored-1-2": {
+        id: "venice-uncensored-1-2",
+        name: "Venice Uncensored 1.2",
+        family: "venice",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-19",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.9 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "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 },
+      },
+      "mistral-small-3-2-24b-instruct": {
+        id: "mistral-small-3-2-24b-instruct",
+        name: "Mistral Small 3.2 24B Instruct",
+        family: "mistral-small",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-01-15",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.09375, output: 0.25 },
+        limit: { context: 256000, output: 16384 },
+      },
+      "minimax-m27": {
+        id: "minimax-m27",
+        name: "MiniMax M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.375, output: 1.5, cache_read: 0.075 },
+        limit: { context: 198000, output: 32768 },
+      },
+      "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 },
+      },
+      "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-04-16",
+        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 },
+      },
+      "mercury-2": {
+        id: "mercury-2",
+        name: "Mercury 2",
+        family: "mercury",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-20",
+        last_updated: "2026-04-09",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3125, output: 0.9375, cache_read: 0.03125 },
+        limit: { context: 128000, output: 50000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "openai-gpt-55-pro": {
+        id: "openai-gpt-55-pro",
+        name: "GPT-5.5 Pro",
+        family: "gpt-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-25",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 37.5, output: 225 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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 },
+      },
+      "venice-uncensored-role-play": {
+        id: "venice-uncensored-role-play",
+        name: "Venice Role Play Uncensored",
+        family: "venice",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-20",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 2 },
+        limit: { context: 128000, output: 4096 },
+      },
+      "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.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: "2024-04-01",
+        last_updated: "2026-04-04",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.85, output: 2.75, cache_read: 0.3 },
+        limit: { context: 198000, output: 16384 },
+      },
+      "grok-4-3": {
+        id: "grok-4-3",
+        name: "Grok 4.3",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-18",
+        last_updated: "2026-05-04",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 1.42,
+          output: 2.83,
+          cache_read: 0.23,
+          context_over_200k: { input: 2.83, output: 5.67, cache_read: 0.45 },
+        },
+        limit: { context: 1000000, output: 32000 },
+      },
+      "mistral-small-2603": {
+        id: "mistral-small-2603",
+        name: "Mistral Small 4",
+        family: "mistral-small",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-16",
+        last_updated: "2026-04-09",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1875, output: 0.75 },
+        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: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-07",
+        release_date: "2025-11-06",
+        last_updated: "2026-05-06",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.07, output: 0.3 },
+        limit: { context: 128000, 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,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-12-06",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 6, output: 30, cache_read: 0.6, cache_write: 7.5 },
+        limit: { context: 198000, output: 32768 },
+      },
+      "qwen3-5-9b": {
+        id: "qwen3-5-9b",
+        name: "Qwen 3.5 9B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-05",
+        last_updated: "2026-04-19",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.15 },
+        limit: { context: 256000, output: 32768 },
+      },
+      "deepseek-v4-flash": {
+        id: "deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-29",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.17, output: 0.35, cache_read: 0.028 },
+        limit: { context: 1000000, output: 32768 },
+      },
+      "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 },
+      },
+      "openai-gpt-54-mini": {
+        id: "openai-gpt-54-mini",
+        name: "GPT-5.4 Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-31",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.9375, output: 5.625, cache_read: 0.09375 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "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-04-12",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.34, output: 1.19, cache_read: 0.04 },
+        limit: { context: 198000, output: 32768 },
+      },
+      "zai-org-glm-5-1": {
+        id: "zai-org-glm-5-1",
+        name: "GLM 5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-07",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.75, output: 5.5, cache_read: 0.325 },
+        limit: { context: 200000, output: 24000 },
+      },
+      "openai-gpt-55": {
+        id: "openai-gpt-55",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-23",
+        last_updated: "2026-04-25",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 6.25,
+          output: 37.5,
+          cache_read: 0.625,
+          context_over_200k: { input: 12.5, output: 56.25, cache_read: 1.25 },
+        },
+        limit: { context: 1000000, output: 131072 },
+      },
+      "qwen3-6-27b": {
+        id: "qwen3-6-27b",
+        name: "Qwen 3.6 27B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-29",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.325, output: 3.25 },
+        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,
+        knowledge: "2025-05-31",
+        release_date: "2026-02-05",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 6, output: 30, cache_read: 0.6, cache_write: 7.5 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-29",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.73, output: 3.796, cache_read: 0.33 },
+        limit: { context: 1000000, output: 32768 },
+      },
+      "deepseek-v3.2": {
+        id: "deepseek-v3.2",
+        name: "DeepSeek V3.2",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-10",
+        release_date: "2025-12-04",
+        last_updated: "2026-03-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.33, output: 0.48, cache_read: 0.16 },
+        limit: { context: 160000, output: 32768 },
+      },
+      "qwen-3-6-plus": {
+        id: "qwen-3-6-plus",
+        name: "Qwen 3.6 Plus Uncensored",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-06",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 0.625,
+          output: 3.75,
+          cache_read: 0.0625,
+          cache_write: 0.78,
+          context_over_200k: { input: 2.5, output: 7.5, cache_read: 0.0625, cache_write: 0.78 },
+        },
+        limit: { context: 1000000, output: 65536 },
+      },
+      "aion-labs-aion-2-0": {
+        id: "aion-labs-aion-2-0",
+        name: "Aion 2.0",
+        family: "o",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-24",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 2, cache_read: 0.25 },
+        limit: { context: 128000, output: 32768 },
+      },
+      "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,
+        release_date: "2025-01-15",
+        last_updated: "2026-04-12",
+        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: 64000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-04-30",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.56, output: 3.5, cache_read: 0.22 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "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 },
+      },
+      "arcee-trinity-large-thinking": {
+        id: "arcee-trinity-large-thinking",
+        name: "Trinity Large Thinking",
+        family: "trinity",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-04",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3125, output: 1.125, cache_read: 0.075 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "kimi-k2-6": {
+        id: "kimi-k2-6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.85, output: 4.655, cache_read: 0.22 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "claude-opus-4-6-fast": {
+        id: "claude-opus-4-6-fast",
+        name: "Claude Opus 4.6 Fast",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05-31",
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 36, output: 180, cache_read: 3.6, cache_write: 45 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "z-ai-glm-5-turbo": {
+        id: "z-ai-glm-5-turbo",
+        name: "GLM 5 Turbo",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-15",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.2, output: 4, cache_read: 0.24 },
+        limit: { context: 200000, output: 32768 },
+      },
+      "google-gemma-4-31b-it": {
+        id: "google-gemma-4-31b-it",
+        name: "Google Gemma 4 31B Instruct",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-03",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.175, output: 0.5 },
+        limit: { context: 256000, output: 8192 },
+      },
+    },
+  },
+  aihubmix: {
+    id: "aihubmix",
+    env: ["AIHUBMIX_API_KEY"],
+    npm: "@aihubmix/ai-sdk-provider",
+    name: "AIHubMix",
+    doc: "https://docs.aihubmix.com",
+    models: {
+      "minimax-m2.7": {
+        id: "minimax-m2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2958, output: 1.1832, cache_read: 0.05916 },
+        limit: { context: 200000, output: 128000 },
+      },
+      "coding-glm-5.1-free": {
+        id: "coding-glm-5.1-free",
+        name: "Coding GLM 5.1 (free)",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-11",
+        last_updated: "2026-04-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 204800, output: 128000 },
+      },
+      "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", "audio", "video", "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 },
+      },
+      "kimi-k2.5": {
+        id: "kimi-k2.5",
+        name: "Kimi K2.5",
+        family: "kimi-k2.5",
+        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.105 },
+        limit: { context: 256000, output: 0 },
+      },
+      "glm-5v-turbo": {
+        id: "glm-5v-turbo",
+        name: "GLM 5 Vision Turbo",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-05-09",
+        last_updated: "2026-05-09",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.7042, output: 3.09848, cache_read: 0.169008 },
+        limit: { context: 200000, output: 128000 },
+      },
+      "grok-4.3": {
+        id: "grok-4.3",
+        name: "Grok 4.3",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-05-01",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 1.25,
+          output: 2.5,
+          cache_read: 0.2,
+          context_over_200k: { input: 2.5, output: 5, cache_read: 0.4 },
+        },
+        limit: { context: 1000000, output: 1000000 },
+      },
+      "coding-minimax-m2.7-highspeed": {
+        id: "coding-minimax-m2.7-highspeed",
+        name: "Coding MiniMax M2.7 Highspeed",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.2 },
+        limit: { context: 204800, output: 13100 },
+      },
+      "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-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: 200000, output: 64000 },
+      },
+      "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", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 12, cache_read: 0.2 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "coding-glm-5.1": {
+        id: "coding-glm-5.1",
+        name: "Coding-GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-11",
+        last_updated: "2026-04-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.06, output: 0.22 },
+        limit: { context: 200000, 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", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 3, cache_read: 0.05 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "gpt-5.5": {
+        id: "gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5 },
+        limit: { context: 1050000, output: 128000 },
+      },
+      "claude-opus-4-7": {
+        id: "claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+      "deepseek-v4-flash-think": {
+        id: "deepseek-v4-flash-think",
+        name: "DeepSeek V4 Flash Think",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.154, output: 0.308, cache_read: 0.0308 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "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 },
+      },
+      "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.125 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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 },
+      },
+      "qwen3.6-plus": {
+        id: "qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-05-09",
+        last_updated: "2026-05-09",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.282, output: 1.692, cache_read: 0.0282, cache_write: 0.3525 },
+        limit: { context: 991000, output: 64000 },
+      },
+      "gpt-5.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "GPT-5.4-Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.845, output: 3.38, cache_read: 0.183112 },
+        limit: { context: 200000, 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.275 },
+        limit: { context: 200000, output: 100000 },
+      },
+      "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,
+        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-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.499, cache_read: 0.03 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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 },
+      },
+      "gemini-3.1-flash-lite": {
+        id: "gemini-3.1-flash-lite",
+        name: "Gemini 3.1 Flash Lite",
+        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", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 1.5, cache_read: 0.25 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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 },
+      },
+      "claude-opus-4-6-think": {
+        id: "claude-opus-4-6-think",
+        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-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: 200000, output: 32000 },
+      },
+      "coding-minimax-m2.7-free": {
+        id: "coding-minimax-m2.7-free",
+        name: "Coding-MiniMax-M2.7-Free",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 204800, output: 13100 },
+      },
+      "deepseek-v4-flash": {
+        id: "deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.154, output: 0.308, cache_read: 0.0308 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 3.9995, cache_read: 0.160835 },
+        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,
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2.5, output: 15, cache_read: 0.25 },
+        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-31",
+        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 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.478, output: 0.956, cache_read: 0.004302 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "claude-opus-4-7-think": {
+        id: "claude-opus-4-7-think",
+        name: "Claude Opus 4.7 Thinking",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+      "coding-minimax-m2.7": {
+        id: "coding-minimax-m2.7",
+        name: "Coding MiniMax M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.2 },
+        limit: { context: 204800, output: 13100 },
+      },
+      "qwen3.6-max-preview": {
+        id: "qwen3.6-max-preview",
+        name: "Qwen3.6 Max Preview",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-05-09",
+        last_updated: "2026-05-09",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.268, output: 7.608, cache_read: 0.1268, cache_write: 1.585 },
+        limit: { context: 240000, 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: 2, output: 8, cache_read: 0.5 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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: 200000, output: 64000 },
+      },
+      "qwen3.6-flash": {
+        id: "qwen3.6-flash",
+        name: "Qwen3.6 Flash",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.169, output: 1.014, cache_read: 0.0169, cache_write: 0.21125 },
+        limit: { context: 991000, output: 64000 },
+      },
+    },
+  },
+  cerebras: {
+    id: "cerebras",
+    env: ["CEREBRAS_API_KEY"],
+    npm: "@ai-sdk/cerebras",
+    name: "Cerebras",
+    doc: "https://inference-docs.cerebras.ai/models/overview",
+    models: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "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-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-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-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.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        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.5": {
+        id: "kimi-k2.5",
+        name: "Kimi K2.5",
+        family: "kimi-k2.5",
+        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 },
+      },
+    },
+  },
+  "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-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-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-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",
+        },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4 },
+        limit: { context: 262144, output: 262144 },
+        provider: {
+          npm: "@ai-sdk/openai-compatible",
+          api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/models",
+          shape: "completions",
+        },
+      },
+      "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,
+          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-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",
+        },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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: {
+        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 },
+      },
+      "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": {
+        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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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-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 },
+      },
+      "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-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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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",
+        },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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 },
+      },
+      "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-5.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "GPT-5.4 Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "gpt-5.4-nano": {
+        id: "gpt-5.4-nano",
+        name: "GPT-5.4 Nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "cohere-command-r-plus-08-2024": {
+        id: "cohere-command-r-plus-08-2024",
+        name: "Command R+",
+        family: "command-r",
+        attachment: false,
+        reasoning: false,
+        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-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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "cohere-command-r-08-2024": {
+        id: "cohere-command-r-08-2024",
+        name: "Command R",
+        family: "command-r",
+        attachment: false,
+        reasoning: false,
+        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-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 },
+      },
+      "gpt-4-turbo": {
+        id: "gpt-4-turbo",
+        name: "GPT-4 Turbo",
+        family: "gpt",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        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.5": {
+        id: "gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+      },
+    },
+  },
+  "abliteration-ai": {
+    id: "abliteration-ai",
+    env: ["ABLIT_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.abliteration.ai/v1",
+    name: "abliteration.ai",
+    doc: "https://docs.abliteration.ai/models",
+    models: {
+      "abliterated-model": {
+        id: "abliterated-model",
+        name: "Abliterated Model",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: false,
+        temperature: true,
+        release_date: "2026-01-06",
+        last_updated: "2026-01-06",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 3, output: 3 },
+        limit: { context: 150000, input: 150000, output: 8192 },
+      },
+    },
+  },
+  "wafer.ai": {
+    id: "wafer.ai",
+    env: ["WAFER_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://pass.wafer.ai/v1",
+    name: "Wafer",
+    doc: "https://docs.wafer.ai/wafer-pass",
+    models: {
+      "Qwen3.5-397B-A17B": {
+        id: "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, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "GLM-5.1": {
+        id: "GLM-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 202752, output: 131072 },
+      },
+      "MiniMax-M2.7": {
+        id: "MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "DeepSeek-V4-Pro": {
+        id: "DeepSeek-V4-Pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 1000000, output: 384000 },
+      },
+    },
+  },
+  cohere: {
+    id: "cohere",
+    env: ["COHERE_API_KEY"],
+    npm: "@ai-sdk/cohere",
+    name: "Cohere",
+    doc: "https://docs.cohere.com/docs/models",
+    models: {
+      "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 },
+      },
+      "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 },
+      },
+      "c4ai-aya-vision-8b": {
+        id: "c4ai-aya-vision-8b",
+        name: "Aya Vision 8B",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        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-r-plus-08-2024": {
+        id: "command-r-plus-08-2024",
+        name: "Command R+",
+        family: "command-r",
+        attachment: false,
+        reasoning: false,
+        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 },
+      },
+      "c4ai-aya-expanse-8b": {
+        id: "c4ai-aya-expanse-8b",
+        name: "Aya Expanse 8B",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        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 },
+      },
+      "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-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 },
+      },
+      "c4ai-aya-vision-32b": {
+        id: "c4ai-aya-vision-32b",
+        name: "Aya Vision 32B",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        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-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: false,
+        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 },
+      },
+      "c4ai-aya-expanse-32b": {
+        id: "c4ai-aya-expanse-32b",
+        name: "Aya Expanse 32B",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        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: false,
+        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 },
+      },
+    },
+  },
+  "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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "MiniMaxAI/MiniMax-M2.5": {
+        id: "MiniMaxAI/MiniMax-M2.5",
+        name: "MiniMax-M2.5",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: 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, input: 180000, output: 16000 },
+      },
+    },
+  },
+  "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 },
+      },
+    },
+  },
+  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-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-4.3": {
+        id: "grok-4.3",
+        name: "Grok 4.3",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-05-01",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 1.25,
+          output: 2.5,
+          cache_read: 0.2,
+          context_over_200k: { input: 2.5, output: 5, cache_read: 0.4 },
+        },
+        limit: { context: 1000000, output: 30000 },
+      },
+      "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-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-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-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-4.20-0309-reasoning": {
+        id: "grok-4.20-0309-reasoning",
+        name: "Grok 4.20 (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 },
+      },
+      "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-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-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-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-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-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-4.20-0309-non-reasoning": {
+        id: "grok-4.20-0309-non-reasoning",
+        name: "Grok 4.20 (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 },
+      },
+      "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-4.20-multi-agent-0309": {
+        id: "grok-4.20-multi-agent-0309",
+        name: "Grok 4.20 Multi-Agent",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: false,
+        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 },
+      },
+      "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-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-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-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-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-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-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-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-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 },
+      },
+    },
+  },
+  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: {
+      "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/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/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "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-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-31",
+        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,
+          context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 },
+        },
+        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-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-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-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-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-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-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-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-31",
+        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,
+          context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 },
+        },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "claude-opus-4-7@default": {
+        id: "claude-opus-4-7@default",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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-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 },
+      },
+      "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/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/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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: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-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.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-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.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-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: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.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.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:Qwen/Qwen3.5-397B-A17B": {
+        id: "hf:Qwen/Qwen3.5-397B-A17B",
+        name: "Qwen3.5-97B-A17B",
+        family: "qwen",
+        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-11",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3, cache_read: 0.6 },
+        limit: { context: 262144, output: 65536 },
+        status: "beta",
+      },
+      "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-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 },
+      },
+      "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/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: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-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-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.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.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: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: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: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:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4": {
+        id: "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
+        name: "Nemotron 3 Super 120B",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2024-04",
+        release_date: "2026-03-11",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1, cache_read: 0.3 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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: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.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:zai-org/GLM-5.1": {
+        id: "hf:zai-org/GLM-5.1",
+        name: "GLM 5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-04-12",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 1 },
+        limit: { context: 196608, output: 65536 },
+      },
+      "hf:zai-org/GLM-5": {
+        id: "hf: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-04-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 1 },
+        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:moonshotai/Kimi-K2.6": {
+        id: "hf:moonshotai/Kimi-K2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.95 },
+        limit: { context: 262144, output: 65536 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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-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/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 },
+      },
+      "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/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/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 },
+      },
+      "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.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-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-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 },
+      },
+      "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-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-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 },
+      },
+      "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": {
+        id: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
+        name: "Nemotron 3 Nano Omni",
+        family: "nemotron",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-04-28",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "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/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/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/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/nemotron-3-super-120b-a12b": {
+        id: "nvidia/nemotron-3-super-120b-a12b",
+        name: "Nemotron 3 Super",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-04",
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.8 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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/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 },
+      },
+      "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/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/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/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.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/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/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 },
+      },
+      "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": {
+        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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "minimaxai/minimax-m2.7": {
+        id: "minimaxai/minimax-m2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-04-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        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,
+        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 },
+      },
+      "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 },
+      },
+      "z-ai/glm-5.1": {
+        id: "z-ai/glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 131072, output: 131072 },
+      },
+      "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.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/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/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/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-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/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.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-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 },
+      },
+      "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 },
+      },
+      "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/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/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.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/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/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/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 },
+      },
+      "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 },
+      },
+      "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-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-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/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-4-31b-it": {
+        id: "google/gemma-4-31b-it",
+        name: "Gemma-4-31B-IT",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 256000, output: 16384 },
+      },
+      "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/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-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/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-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 },
+      },
+      "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-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.6": {
+        id: "moonshotai/kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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-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 },
+      },
+      "mistralai/mistral-medium-3.5-128b": {
+        id: "mistralai/mistral-medium-3.5-128b",
+        name: "Mistral Medium 3.5 128B",
+        family: "mistral-medium",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-29",
+        last_updated: "2026-04-29",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "deepseek-ai/deepseek-v4-flash": {
+        id: "deepseek-ai/deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1048576, output: 393216 },
+      },
+      "deepseek-ai/deepseek-v4-pro": {
+        id: "deepseek-ai/deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1048576, output: 393216 },
+      },
+    },
+  },
+  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 },
+      },
+      "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-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.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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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-edit-2": {
+        id: "mercury-edit-2",
+        name: "Mercury Edit 2",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-30",
+        last_updated: "2026-03-30",
+        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-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 },
+      },
+    },
+  },
+  openai: {
+    id: "openai",
+    env: ["OPENAI_API_KEY"],
+    npm: "@ai-sdk/openai",
+    name: "OpenAI",
+    doc: "https://platform.openai.com/docs/models",
+    models: {
+      "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 },
+      },
+      "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-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 },
+      },
+      "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-5.3-chat-latest": {
+        id: "gpt-5.3-chat-latest",
+        name: "GPT-5.3 Chat (latest)",
+        family: "gpt",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        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 },
+      },
+      "gpt-5.5": {
+        id: "gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+        experimental: {
+          modes: {
+            fast: {
+              cost: { input: 12.5, output: 75, cache_read: 1.25 },
+              provider: { body: { service_tier: "priority" } },
+            },
+          },
+        },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "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.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 },
+      },
+      "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-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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.15, output: 0.6, cache_read: 0.08 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "GPT-5.4 mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+        experimental: {
+          modes: {
+            fast: {
+              cost: { input: 1.5, output: 9, cache_read: 0.15 },
+              provider: { body: { service_tier: "priority" } },
+            },
+          },
+        },
+      },
+      "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-5.4-nano": {
+        id: "gpt-5.4-nano",
+        name: "GPT-5.4 nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "gpt-image-1": {
+        id: "gpt-image-1",
+        name: "gpt-image-1",
+        family: "gpt-image",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-04-24",
+        last_updated: "2025-04-24",
+        modalities: { input: ["text", "image"], output: ["image"] },
+        open_weights: false,
+        limit: { context: 0, input: 0, output: 0 },
+      },
+      "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 },
+      },
+      "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.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 },
+      },
+      "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-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.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 },
+      },
+      "gpt-image-1-mini": {
+        id: "gpt-image-1-mini",
+        name: "gpt-image-1-mini",
+        family: "gpt-image",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-09-26",
+        last_updated: "2025-09-26",
+        modalities: { input: ["text", "image"], output: ["text", "image"] },
+        open_weights: false,
+        limit: { context: 0, input: 0, output: 0 },
+      },
+      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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 15, output: 60, cache_read: 7.5 },
+        limit: { context: 200000, output: 100000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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.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 },
+        experimental: {
+          modes: {
+            fast: { cost: { input: 5, output: 30, cache_read: 0.5 }, provider: { body: { service_tier: "priority" } } },
+          },
+        },
+      },
+      "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-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 },
+      },
+      "chatgpt-image-latest": {
+        id: "chatgpt-image-latest",
+        name: "chatgpt-image-latest",
+        family: "gpt-image",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-12-16",
+        last_updated: "2025-12-16",
+        modalities: { input: ["text", "image"], output: ["text", "image"] },
+        open_weights: false,
+        limit: { context: 0, input: 0, output: 0 },
+      },
+      "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 },
+      },
+      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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 8, cache_read: 0.5 },
+        limit: { context: 200000, output: 100000 },
+      },
+      "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": {
+        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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2.5, output: 10, cache_read: 1.25 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "gpt-image-1.5": {
+        id: "gpt-image-1.5",
+        name: "gpt-image-1.5",
+        family: "gpt-image",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-11-25",
+        last_updated: "2025-11-25",
+        modalities: { input: ["text", "image"], output: ["text", "image"] },
+        open_weights: false,
+        limit: { context: 0, input: 0, output: 0 },
+      },
+      "gpt-5.5-pro": {
+        id: "gpt-5.5-pro",
+        name: "GPT-5.5 Pro",
+        family: "gpt-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 30, output: 180, context_over_200k: { input: 60, output: 270 } },
+        limit: { context: 1050000, input: 922000, 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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 8, cache_read: 0.5 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 1.6, cache_read: 0.1 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "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-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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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-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.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 },
+      },
+      "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-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-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/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-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-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-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": {
+        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.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-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": {
+        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/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.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 },
+      },
+      "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-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 },
+      },
+      "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-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,
+        knowledge: "2025-08-31",
+        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-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-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-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-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-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 },
+      },
+      "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.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-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-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-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 },
+      },
+      "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.31,
+          cache_write: 2.375,
+          context_over_200k: { input: 2.5, output: 15, cache_read: 0.25 },
+        },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  digitalocean: {
+    id: "digitalocean",
+    env: ["DIGITALOCEAN_ACCESS_TOKEN"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://inference.do-ai.run/v1",
+    name: "DigitalOcean",
+    doc: "https://docs.digitalocean.com/products/gradient-ai-platform/details/models/",
+    models: {
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.15, output: 0.6, cache_read: 0.075 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "multi-qa-mpnet-base-dot-v1": {
+        id: "multi-qa-mpnet-base-dot-v1",
+        name: "Multi-QA-mpnet-base-dot-v1",
+        family: "text-embedding",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2021-08-30",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.009, output: 0 },
+        limit: { context: 512, output: 768 },
+      },
+      "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-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 2.7 },
+        limit: { context: 262144, output: 32768 },
+      },
+      "nemotron-3-nano-omni": {
+        id: "nemotron-3-nano-omni",
+        name: "Nemotron Nano 3 Omni",
+        family: "nemotron",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-28",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 0.9 },
+        limit: { context: 65536, output: 65536 },
+      },
+      "llama3-8b-instruct": {
+        id: "llama3-8b-instruct",
+        name: "Llama 3.1 Instruct (8B)",
+        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.198, output: 0.198 },
+        limit: { context: 131072, output: 131072 },
+      },
+      "anthropic-claude-opus-4.7": {
+        id: "anthropic-claude-opus-4.7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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": {
+        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.3, cache_write: 3.75 },
+        },
+        limit: { context: 1000000, output: 64000 },
+      },
+      "wan2-2-t2v-a14b": {
+        id: "wan2-2-t2v-a14b",
+        name: "Wan2.2-T2V-A14B",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-07-28",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["video"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 0 },
+        limit: { context: 100, output: 1 },
+      },
+      "qwen-2.5-14b-instruct": {
+        id: "qwen-2.5-14b-instruct",
+        name: "Qwen 2.5 14B Instruct",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-09",
+        release_date: "2024-09-19",
+        last_updated: "2024-09-19",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 131072, output: 131072 },
+      },
+      "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: 1000000, output: 128000 },
+      },
+      "qwen3.5-397b-a17b": {
+        id: "qwen3.5-397b-a17b",
+        name: "Qwen 3.5 397B A17B",
+        family: "qwen3.5",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-15",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.55, output: 3.5 },
+        limit: { context: 262144, output: 81920 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 8, cache_read: 0.5 },
+        limit: { context: 200000, output: 100000 },
+      },
+      "e5-large-v2": {
+        id: "e5-large-v2",
+        name: "E5 Large v2",
+        family: "text-embedding",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2023-05-19",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.02, output: 0 },
+        limit: { context: 512, output: 1024 },
+      },
+      "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,
+        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 },
+      },
+      "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-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3.2 },
+        limit: { context: 202752, output: 128000 },
+      },
+      "openai-gpt-5.4-nano": {
+        id: "openai-gpt-5.4-nano",
+        name: "GPT-5.4 nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "mistral-7b-instruct-v0.3": {
+        id: "mistral-7b-instruct-v0.3",
+        name: "Mistral 7B Instruct v0.3",
+        family: "mistral",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2024-05-22",
+        last_updated: "2024-05-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 32768, output: 32768 },
+      },
+      "llama3.3-70b-instruct": {
+        id: "llama3.3-70b-instruct",
+        name: "Llama 3.3 Instruct 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.65, output: 0.65 },
+        limit: { context: 128000, output: 128000 },
+      },
+      "mistral-3-14B": {
+        id: "mistral-3-14B",
+        name: "Ministral 3 14B Instruct",
+        family: "ministral",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-12-15",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.2 },
+        limit: { context: 262144, 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,
+        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.99, output: 0.99 },
+        limit: { context: 131072, output: 32768 },
+      },
+      "alibaba-qwen3-32b": {
+        id: "alibaba-qwen3-32b",
+        name: "Qwen3-32B",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-04-30",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 0.55 },
+        limit: { context: 131000, output: 40960 },
+      },
+      "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 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 15, output: 60, cache_read: 7.5 },
+        limit: { context: 200000, output: 100000 },
+      },
+      "anthropic-claude-3-opus": {
+        id: "anthropic-claude-3-opus",
+        name: "Claude 3 Opus",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2023-08",
+        release_date: "2024-02-29",
+        last_updated: "2024-02-29",
+        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: 4096 },
+        status: "deprecated",
+      },
+      "stable-diffusion-3.5-large": {
+        id: "stable-diffusion-3.5-large",
+        name: "Stable Diffusion 3.5 Large",
+        family: "stable-diffusion",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2024-10-22",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["image"] },
+        open_weights: true,
+        cost: { input: 0.08, output: 0 },
+        limit: { context: 256, output: 1 },
+      },
+      "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, output: 128000 },
+      },
+      "llama-4-maverick": {
+        id: "llama-4-maverick",
+        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-05",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 0.87 },
+        limit: { context: 1000000, output: 16384 },
+      },
+      "anthropic-claude-4.5-sonnet": {
+        id: "anthropic-claude-4.5-sonnet",
+        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,
+          context_over_200k: { input: 6, output: 22.5, cache_read: 0.3, cache_write: 3.75 },
+        },
+        limit: { context: 1000000, output: 64000 },
+      },
+      "qwen3-embedding-0.6b": {
+        id: "qwen3-embedding-0.6b",
+        name: "Qwen3 Embedding 0.6B",
+        family: "text-embedding",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-06-03",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.04, output: 0 },
+        limit: { context: 8000, output: 1024 },
+        status: "beta",
+      },
+      "anthropic-claude-4.5-haiku": {
+        id: "anthropic-claude-4.5-haiku",
+        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: 1, output: 5, cache_read: 1, cache_write: 1.25 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "gte-large-en-v1.5": {
+        id: "gte-large-en-v1.5",
+        name: "GTE Large (v1.5)",
+        family: "text-embedding",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2024-03-27",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.09, output: 0 },
+        limit: { context: 8192, output: 1024 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 8, cache_read: 0.5 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "anthropic-claude-3.5-haiku": {
+        id: "anthropic-claude-3.5-haiku",
+        name: "Claude 3.5 Haiku",
+        family: "claude-haiku",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-07",
+        release_date: "2024-11-05",
+        last_updated: "2024-11-05",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 },
+        limit: { context: 200000, output: 8192 },
+        status: "deprecated",
+      },
+      "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 },
+      },
+      "deepseek-3.2": {
+        id: "deepseek-3.2",
+        name: "DeepSeek V3.2",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2025-12-02",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 1.6 },
+        limit: { context: 128000, output: 64000 },
+      },
+      "nemotron-3-nano-30b": {
+        id: "nemotron-3-nano-30b",
+        name: "Nemotron 3 Nano 30B A3B",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-04-14",
+        last_updated: "2025-04-14",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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,
+        knowledge: "2024-06",
+        release_date: "2025-08-05",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.05, output: 0.45 },
+        limit: { context: 131072, output: 131072 },
+      },
+      "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: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.45, output: 1.7 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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-oss-120b": {
+        id: "openai-gpt-oss-120b",
+        name: "gpt-oss-120b",
+        family: "gpt-oss",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2024-06",
+        release_date: "2025-08-05",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.7 },
+        limit: { context: 131072, output: 131072 },
+      },
+      "gemma-4-31B-it": {
+        id: "gemma-4-31B-it",
+        name: "Gemma 4 31B",
+        family: "gemma",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-22",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.18, output: 0.5 },
+        limit: { context: 256000, output: 8192 },
+      },
+      "nemotron-nano-12b-v2-vl": {
+        id: "nemotron-nano-12b-v2-vl",
+        name: "Nemotron Nano 12B v2 VL",
+        family: "nemotron",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-10",
+        release_date: "2025-12-01",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.6 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "anthropic-claude-4.1-opus": {
+        id: "anthropic-claude-4.1-opus",
+        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 },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "openai-gpt-image-2": {
+        id: "openai-gpt-image-2",
+        name: "GPT Image 2",
+        family: "gpt-image",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-04-24",
+        last_updated: "2025-04-24",
+        modalities: { input: ["text", "image"], output: ["image"] },
+        open_weights: false,
+        limit: { context: 0, output: 0 },
+      },
+      "anthropic-claude-4.6-sonnet": {
+        id: "anthropic-claude-4.6-sonnet",
+        name: "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-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,
+          context_over_200k: { input: 6, output: 22.5, cache_read: 0.3, cache_write: 3.75 },
+        },
+        limit: { context: 1000000, output: 64000 },
+      },
+      "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, 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: 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: 1, cache_write: 1.25 },
+        limit: { context: 200000, output: 64000 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48 },
+        limit: { context: 1048576, output: 393216 },
+      },
+      "ministral-3-8b-instruct-2512": {
+        id: "ministral-3-8b-instruct-2512",
+        name: "Ministral 3 8B",
+        family: "ministral",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-12-15",
+        last_updated: "2025-12-15",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 262144, output: 262144 },
+      },
+      "minimax-m2.5": {
+        id: "minimax-m2.5",
+        name: "MiniMax M2.5",
+        family: "minimax-m2.5",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-08",
+        release_date: "2026-02-12",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2 },
+        limit: { context: 204800, output: 128000 },
+        status: "beta",
+      },
+      "openai-gpt-image-1": {
+        id: "openai-gpt-image-1",
+        name: "GPT Image 1",
+        family: "gpt-image",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-04-24",
+        last_updated: "2025-04-24",
+        modalities: { input: ["text", "image"], output: ["image"] },
+        open_weights: false,
+        cost: { input: 5, output: 40, cache_read: 1.25 },
+        limit: { context: 0, output: 0 },
+      },
+      "openai-gpt-5.5": {
+        id: "openai-gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "nvidia-nemotron-3-super-120b": {
+        id: "nvidia-nemotron-3-super-120b",
+        name: "Nemotron-3-Super-120B",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2026-02",
+        release_date: "2026-03-11",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 0.65 },
+        limit: { context: 256000, output: 32768 },
+        status: "beta",
+      },
+      "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,
+        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, output: 128000 },
+      },
+      "all-mini-lm-l6-v2": {
+        id: "all-mini-lm-l6-v2",
+        name: "All-MiniLM-L6-v2",
+        family: "text-embedding",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2021-08-30",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.009, output: 0 },
+        limit: { context: 256, output: 384 },
+      },
+      "bge-m3": {
+        id: "bge-m3",
+        name: "BGE M3",
+        family: "bge",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2024-01-30",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.02, output: 0 },
+        limit: { context: 8192, output: 1024 },
+      },
+      "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: 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 },
+      },
+      "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-31",
+        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,
+          context_over_200k: { input: 10, output: 37.5, cache_read: 0.5, cache_write: 6.25 },
+        },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2.5, output: 10, cache_read: 1.25 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "openai-gpt-5.4-mini": {
+        id: "openai-gpt-5.4-mini",
+        name: "GPT-5.4 mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, output: 128000 },
+      },
+      "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, output: 128000 },
+      },
+      "arcee-trinity-large-thinking": {
+        id: "arcee-trinity-large-thinking",
+        name: "Trinity Large Thinking",
+        family: "trinity",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 0.9, cache_read: 0.06 },
+        limit: { context: 256000, output: 128000 },
+        status: "beta",
+      },
+      "mistral-nemo-instruct-2407": {
+        id: "mistral-nemo-instruct-2407",
+        name: "Mistral Nemo Instruct",
+        family: "mistral",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2024-07-18",
+        last_updated: "2024-07-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 0.3 },
+        limit: { context: 128000, output: 16384 },
+        status: "deprecated",
+      },
+      "deepseek-v3": {
+        id: "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: "2025-03-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 163840, output: 131072 },
+      },
+      "bge-reranker-v2-m3": {
+        id: "bge-reranker-v2-m3",
+        name: "BGE Reranker v2 M3",
+        family: "bge",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2024-03-12",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.01, output: 0 },
+        limit: { context: 8192, output: 1 },
+      },
+      "anthropic-claude-3.7-sonnet": {
+        id: "anthropic-claude-3.7-sonnet",
+        name: "Claude 3.7 Sonnet",
+        family: "claude-sonnet",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-11",
+        release_date: "2025-02-24",
+        last_updated: "2025-02-24",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
+        limit: { context: 200000, output: 64000 },
+        status: "deprecated",
+      },
+      "qwen3-tts-voicedesign": {
+        id: "qwen3-tts-voicedesign",
+        name: "Qwen3 TTS VoiceDesign",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2026-04-21",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["audio"] },
+        open_weights: true,
+        limit: { context: 32768, output: 1 },
+      },
+      "openai-gpt-image-1.5": {
+        id: "openai-gpt-image-1.5",
+        name: "GPT Image 1.5",
+        family: "gpt-image",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-11-25",
+        last_updated: "2025-11-25",
+        modalities: { input: ["text", "image"], output: ["image"] },
+        open_weights: false,
+        cost: { input: 5, output: 10, cache_read: 1 },
+        limit: { context: 0, output: 0 },
+      },
+      "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, output: 128000 },
+      },
+      "anthropic-claude-3.5-sonnet": {
+        id: "anthropic-claude-3.5-sonnet",
+        name: "Claude 3.5 Sonnet",
+        family: "claude-sonnet",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-04",
+        release_date: "2024-06-20",
+        last_updated: "2024-10-22",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
+        limit: { context: 200000, output: 8192 },
+        status: "deprecated",
+      },
+      "fal-ai/fast-sdxl": {
+        id: "fal-ai/fast-sdxl",
+        name: "Fast SDXL",
+        family: "stable-diffusion",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2023-07-26",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["image"] },
+        open_weights: true,
+        limit: { context: 0, output: 0 },
+      },
+      "fal-ai/flux/schnell": {
+        id: "fal-ai/flux/schnell",
+        name: "FLUX.1 [schnell]",
+        family: "flux",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2024-08-01",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["image"] },
+        open_weights: true,
+        limit: { context: 0, output: 0 },
+      },
+      "fal-ai/elevenlabs/tts/multilingual-v2": {
+        id: "fal-ai/elevenlabs/tts/multilingual-v2",
+        name: "ElevenLabs Multilingual TTS v2",
+        family: "elevenlabs",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2023-08-22",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["audio"] },
+        open_weights: false,
+        limit: { context: 0, output: 0 },
+      },
+      "fal-ai/stable-audio-25/text-to-audio": {
+        id: "fal-ai/stable-audio-25/text-to-audio",
+        name: "Stable Audio 2.5 (Text-to-Audio)",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2025-10-08",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text"], output: ["audio"] },
+        open_weights: false,
+        limit: { context: 0, output: 0 },
+      },
+    },
+  },
+  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: {
+      "MiniMax-M2.5": {
+        id: "MiniMax-M2.5",
+        name: "MiniMax M2.5",
+        family: "minimax",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-09",
+        release_date: "2025-02-11",
+        last_updated: "2025-02-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2 },
+        limit: { context: 194000, output: 4096 },
+      },
+      "GLM-5-FP8": {
+        id: "GLM-5-FP8",
+        name: "GLM 5 FP8",
+        family: "glm",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-02-11",
+        last_updated: "2026-02-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.85, output: 3.1 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "DeepSeek-V3.2": {
+        id: "DeepSeek-V3.2",
+        name: "DeepSeek V3.2",
+        family: "deepseek",
+        attachment: false,
+        reasoning: false,
+        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.55, output: 1.65 },
+        limit: { context: 127000, output: 4096 },
+      },
+      "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-06",
+        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: 129000, output: 4096 },
+      },
+      "Kimi-K2.5": {
+        id: "Kimi-K2.5",
+        name: "Kimi K2 Instruct",
+        family: "kimi",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-04",
+        release_date: "2026-01-27",
+        last_updated: "2026-01-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.55, output: 2.75 },
+        limit: { context: 254000, output: 32768 },
+      },
+    },
+  },
+  "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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.6-plus": {
+        id: "qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  mistral: {
+    id: "mistral",
+    env: ["MISTRAL_API_KEY"],
+    npm: "@ai-sdk/mistral",
+    name: "Mistral",
+    doc: "https://docs.mistral.ai/getting-started/models/",
+    models: {
+      "mistral-small-latest": {
+        id: "mistral-small-latest",
+        name: "Mistral Small (latest)",
+        family: "mistral-small",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-06",
+        release_date: "2026-03-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.6 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-small-2603": {
+        id: "mistral-small-2603",
+        name: "Mistral Small 4",
+        family: "mistral-small",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-06",
+        release_date: "2026-03-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.6 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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 },
+      },
+      "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-2604": {
+        id: "mistral-medium-2604",
+        name: "Mistral Medium 3.5",
+        family: "mistral-medium",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-29",
+        last_updated: "2026-04-29",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.5, output: 7.5 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-medium-latest": {
+        id: "mistral-medium-latest",
+        name: "Mistral Medium (latest)",
+        family: "mistral-medium",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-29",
+        last_updated: "2026-04-29",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.5, output: 7.5 },
+        limit: { context: 262144, output: 262144 },
+      },
+    },
+  },
+  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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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.5-9b": {
+        id: "qwen3.5-9b",
+        name: "Qwen3.5-9B",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-15",
+        last_updated: "2026-02-15",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.15 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "zai-org/GLM-5.1": {
+        id: "zai-org/GLM-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        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, cache_read: 0.5 },
+        limit: { context: 202752, output: 202752 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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, cache_read: 0.06 },
+        limit: { context: 196608, output: 196608 },
+      },
+    },
+  },
+  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: {
+      "minimax-m2.7": {
+        id: "minimax-m2.7",
+        name: "MiniMax-m2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.47, output: 1.4 },
+        limit: { context: 202752, output: 196072 },
+      },
+      "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.09, output: 5.43 },
+        limit: { context: 200000, output: 200000 },
+      },
+      "qwen3-235b-a22b-instruct-2507": {
+        id: "qwen3-235b-a22b-instruct-2507",
+        name: "Qwen3 235B A22B Instruct 2507",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        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.062, output: 0.408 },
+        limit: { context: 131000, output: 131000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "claude-opus4-7": {
+        id: "claude-opus4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5.6, output: 27.99, cache_read: 0.56, cache_write: 6.99 },
+        limit: { context: 1000000, 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: true,
+        cost: { input: 1.08, output: 3.44 },
+        limit: { context: 202752, output: 202752 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "codestral-2508": {
+        id: "codestral-2508",
+        name: "Codestral 2508",
+        family: "mistral",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-03",
+        release_date: "2025-07-30",
+        last_updated: "2025-07-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 0.9, cache_read: 0.03 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "nemotron-3-super-120b-a12b": {
+        id: "nemotron-3-super-120b-a12b",
+        name: "Nemotron 3 Super 120B A12B",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-12",
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.266, output: 0.799 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "claude-opus4-6": {
+        id: "claude-opus4-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-03-13",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5.98, output: 29.89 },
+        limit: { context: 1000000, output: 1000000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-14",
+        last_updated: "2026-04-14",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.31, output: 4.1, cache_read: 0.24 },
+        limit: { context: 204800, 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 },
+      },
+      "claude-opus4-5": {
+        id: "claude-opus4-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.98, output: 29.89 },
+        limit: { context: 200000, output: 200000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-17",
+        last_updated: "2026-04-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.81, output: 3.54, cache_read: 0.2 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "qwen3-coder-next": {
+        id: "qwen3-coder-next",
+        name: "Qwen3 Coder Next 80B",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-02-04",
+        last_updated: "2026-02-04",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.158, output: 0.84 },
+        limit: { context: 256000, output: 65536 },
+      },
+      "claude-4-6-sonnet": {
+        id: "claude-4-6-sonnet",
+        name: "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-03-13",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3.59, output: 17.92 },
+        limit: { context: 1000000, output: 1000000 },
+      },
+      "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 },
+      },
+      "mixtral-8x7B-instruct-v0.1": {
+        id: "mixtral-8x7B-instruct-v0.1",
+        name: "Mixtral 8x7B Instruct v0.1",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2023-09",
+        release_date: "2023-12-11",
+        last_updated: "2023-12-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.438, output: 0.68 },
+        limit: { context: 32000, output: 32000 },
+      },
+      "hermes-4-70b": {
+        id: "hermes-4-70b",
+        name: "Hermes 4 70B",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2023-12",
+        release_date: "2025-08-26",
+        last_updated: "2025-08-26",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.116, output: 0.358 },
+        limit: { context: 128000, output: 128000 },
+      },
+      "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.32, output: 1.18 },
+        limit: { context: 196608, output: 196608 },
+      },
+      "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.266, output: 0.444 },
+        limit: { context: 163840, output: 163840 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "qwen-2.5-72b-instruct": {
+        id: "qwen-2.5-72b-instruct",
+        name: "Qwen2.5 72B Instruct",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2024-06",
+        release_date: "2024-09-19",
+        last_updated: "2024-09-19",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.062, output: 0.231 },
+        limit: { context: 33000, output: 33000 },
+      },
+      "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: 0.585, output: 2.307 },
+        limit: { context: 164000, output: 164000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "qwen3.5-122b-a10b": {
+        id: "qwen3.5-122b-a10b",
+        name: "Qwen3.5 122B A10B",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2026-01",
+        release_date: "2026-02-24",
+        last_updated: "2026-02-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.444, output: 3.106 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "llama-3.3-70b-instruct": {
+        id: "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.089, output: 0.275 },
+        limit: { context: 131000, output: 131000 },
+      },
+      "mistral-large-2512": {
+        id: "mistral-large-2512",
+        name: "Mistral Large 3 2512",
+        family: "mistral-large",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-12",
+        release_date: "2025-12-01",
+        last_updated: "2025-12-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 1.5, cache_read: 0.05 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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: "2026-01",
+        release_date: "2026-02-16",
+        last_updated: "2026-02-16",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3.6 },
+        limit: { context: 250000, output: 250000 },
+      },
+      "deepseek-v4-flash": {
+        id: "deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.133, output: 0.266, cache_read: 0.028 },
+        limit: { context: 1048576, output: 384000 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.553, output: 3.106, cache_read: 0.145 },
+        limit: { context: 1048576, output: 384000 },
+      },
+      "qwen3-coder-30b-a3b-instruct": {
+        id: "qwen3-coder-30b-a3b-instruct",
+        name: "Qwen3 Coder 30B A3B Instruct",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        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.053, output: 0.222 },
+        limit: { context: 262000, output: 262000 },
+      },
+    },
+  },
+  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 },
+      },
+      "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/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-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-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/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-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-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/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-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-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-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/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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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/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/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-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-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-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/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 },
+      },
+      "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 },
+      },
+      "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-5V-Turbo": {
+        id: "zai-org/GLM-5V-Turbo",
+        name: "zai-org/GLM-5V-Turbo",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.2, output: 4, cache_write: 0 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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-5.1": {
+        id: "zai-org/GLM-5.1",
+        name: "zai-org/GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4, cache_write: 0 },
+        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 },
+      },
+      "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 },
+      },
+      "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.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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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": {
+        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-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": {
+        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-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-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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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.6": {
+        id: "moonshotai/Kimi-K2.6",
+        name: "moonshotai/Kimi-K2.6",
+        family: "kimi",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262000, output: 262000 },
+      },
+      "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,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        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.25 },
+        limit: { context: 262000, output: 262000 },
+      },
+      "MiniMaxAI/MiniMax-M2.5": {
+        id: "MiniMaxAI/MiniMax-M2.5",
+        name: "MiniMaxAI/MiniMax-M2.5",
+        family: "minimax",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: false,
+        temperature: true,
+        release_date: "2026-02-15",
+        last_updated: "2026-02-15",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3, output: 1.2 },
+        limit: { context: 197000, output: 131000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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.6-27b": {
+        id: "alibaba/qwen3.6-27b",
+        name: "Qwen 3.6 27B",
+        family: "qwen3.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-22",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.6, output: 3.5999999999999996 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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/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/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.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.6-plus": {
+        id: "alibaba/qwen3.6-plus",
+        name: "Qwen 3.6 Plus",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 3, cache_read: 0.09999999999999999, cache_write: 0.625 },
+        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/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-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/qwen-3.6-max-preview": {
+        id: "alibaba/qwen-3.6-max-preview",
+        name: "Qwen 3.6 Max Preview",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.3, output: 7.8, cache_read: 0.26, cache_write: 1.625 },
+        limit: { context: 240000, output: 64000 },
+      },
+      "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-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-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-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-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-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-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-235b-a22b-instruct": {
+        id: "alibaba/qwen3-vl-235b-a22b-instruct",
+        name: "Qwen3 VL 235B A22B Instruct",
+        family: "qwen",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        release_date: "2025-09-24",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.39999999999999997, output: 1.5999999999999999 },
+        limit: { context: 131072, output: 129024 },
+      },
+      "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/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.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/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-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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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-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-v4-flash": {
+        id: "deepseek/deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-23",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "deepseek/deepseek-v4-pro": {
+        id: "deepseek/deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-23",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "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": {
+        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-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-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 },
+      },
+      "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 },
+      },
+      "arcee-ai/trinity-large-thinking": {
+        id: "arcee-ai/trinity-large-thinking",
+        name: "Trinity Large Thinking",
+        family: "trinity",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 0.8999999999999999 },
+        limit: { context: 262100, output: 80000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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-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-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-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-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 },
+      },
+      "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-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-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 },
+      },
+      "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 },
+      },
+      "zai/glm-5v-turbo": {
+        id: "zai/glm-5v-turbo",
+        name: "GLM 5V Turbo",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.2, output: 4, cache_read: 0.24 },
+        limit: { context: 200000, output: 128000 },
+      },
+      "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-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-5.1": {
+        id: "zai/glm-5.1",
+        name: "GLM 5.1",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-07",
+        last_updated: "2026-04-16",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.4, output: 4.4, cache_read: 0.26 },
+        limit: { context: 202752, output: 202752 },
+      },
+      "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.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.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-5-turbo": {
+        id: "zai/glm-5-turbo",
+        name: "GLM 5 Turbo",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-15",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.2, output: 4, cache_read: 0.24 },
+        limit: { context: 202800, output: 131100 },
+      },
+      "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.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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "xai/grok-4.3": {
+        id: "xai/grok-4.3",
+        name: "Grok 4.3",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-30",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.25, output: 2.5, cache_read: 0.19999999999999998 },
+        limit: { context: 1000000, output: 1000000 },
+      },
+      "xai/grok-4.20-non-reasoning": {
+        id: "xai/grok-4.20-non-reasoning",
+        name: "Grok 4.20 Non-Reasoning",
+        family: "grok",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-09",
+        last_updated: "2026-03-23",
+        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-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-4.20-reasoning": {
+        id: "xai/grok-4.20-reasoning",
+        name: "Grok 4.20 Reasoning",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-09",
+        last_updated: "2026-03-23",
+        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.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-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-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.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-4.20-multi-agent": {
+        id: "xai/grok-4.20-multi-agent",
+        name: "Grok 4.20 Multi-Agent",
+        family: "grok",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-09",
+        last_updated: "2026-03-23",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6, cache_read: 0.19999999999999998 },
+        limit: { context: 2000000, output: 2000000 },
+      },
+      "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-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": {
+        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-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-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 },
+      },
+      "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-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-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-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 },
+      },
+      "nvidia/nemotron-3-super-120b-a12b": {
+        id: "nvidia/nemotron-3-super-120b-a12b",
+        name: "NVIDIA Nemotron 3 Super 120B A12B",
+        family: "nemotron",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.15, output: 0.65 },
+        limit: { context: 256000, output: 32000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "inception/mercury-edit-2": {
+        id: "inception/mercury-edit-2",
+        name: "Mercury Edit 2",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-30",
+        last_updated: "2026-03-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 0.75, cache_read: 0.025 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "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,
+        release_date: "2025-02-26",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 1 },
+        limit: { context: 32000, 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-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-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/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-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/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-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/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-5.5": {
+        id: "openai/gpt-5.5",
+        name: "GPT 5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5 },
+        limit: { context: 1000000, input: 872000, output: 128000 },
+      },
+      "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/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-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.4-mini": {
+        id: "openai/gpt-5.4-mini",
+        name: "GPT 5.4 Mini",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "openai/gpt-5.4-nano": {
+        id: "openai/gpt-5.4-nano",
+        name: "GPT 5.4 Nano",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.19999999999999998, output: 1.25, cache_read: 0.02 },
+        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/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.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-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-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/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/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/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/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-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/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-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.5-pro": {
+        id: "openai/gpt-5.5-pro",
+        name: "GPT 5.5 Pro",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 30, output: 180 },
+        limit: { context: 1000000, input: 872000, output: 128000 },
+      },
+      "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-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-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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 1.6, cache_read: 0.1 },
+        limit: { context: 1047576, 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", "pdf"], 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: 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-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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2.5, output: 10, cache_read: 1.25 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 8, cache_read: 0.5 },
+        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,
+        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/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 },
+      },
+      "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/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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 15, output: 60, cache_read: 7.5 },
+        limit: { context: 200000, output: 100000 },
+      },
+      "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-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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.15, output: 0.6, cache_read: 0.08 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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-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 },
+      },
+      "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-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/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-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-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 },
+      },
+      "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/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/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/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/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/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/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-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/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/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/mistral-small": {
+        id: "mistral/mistral-small",
+        name: "Mistral Small (latest)",
+        family: "mistral-small",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-06",
+        release_date: "2026-03-16",
+        last_updated: "2026-03-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.6 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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/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/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/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/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 },
+      },
+      "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 },
+      },
+      "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.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-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-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-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.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-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-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 },
+      },
+      "minimax/minimax-m2.7": {
+        id: "minimax/minimax-m2.7",
+        name: "Minimax M2.7",
+        family: "minimax",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06, cache_write: 0.375 },
+        limit: { context: 204800, output: 131000 },
+      },
+      "minimax/minimax-m2.7-highspeed": {
+        id: "minimax/minimax-m2.7-highspeed",
+        name: "MiniMax M2.7 High Speed",
+        family: "minimax",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 2.4, cache_read: 0.06, cache_write: 0.375 },
+        limit: { context: 204800, output: 131100 },
+      },
+      "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.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.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": {
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "kwaipilot/kat-coder-pro-v2": {
+        id: "kwaipilot/kat-coder-pro-v2",
+        name: "Kat Coder Pro V2",
+        family: "kat-coder",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "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/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/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-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-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/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-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/gemma-4-31b-it": {
+        id: "google/gemma-4-31b-it",
+        name: "Gemma 4 31B IT",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.14, output: 0.39999999999999997 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "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/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/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-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": {
+        id: "google/gemini-3.1-flash-lite",
+        name: "Gemini 3.1 Flash Lite",
+        family: "gemini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-05-07",
+        last_updated: "2026-05-08",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.25, output: 1.5, cache_read: 0.03 },
+        limit: { context: 1000000, output: 65000 },
+      },
+      "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-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-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-embedding-2": {
+        id: "google/gemini-embedding-2",
+        name: "Gemini Embedding 2",
+        family: "gemini-embedding",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-10",
+        last_updated: "2026-03-23",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        limit: { context: 0, output: 0 },
+      },
+      "google/gemma-4-26b-a4b-it": {
+        id: "google/gemma-4-26b-a4b-it",
+        name: "Gemma 4 26B A4B IT",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-03",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.13, output: 0.39999999999999997 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "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/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-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/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.03, input_audio: 1 },
+        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,
+        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.125,
+          context_over_200k: { input: 2.5, output: 15, cache_read: 0.25 },
+        },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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 },
+      },
+      "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.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-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-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.6": {
+        id: "moonshotai/kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262000, output: 262000 },
+      },
+      "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": {
+        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",
+      },
+      "interfaze/interfaze-beta": {
+        id: "interfaze/interfaze-beta",
+        name: "Interfaze Beta",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        temperature: true,
+        release_date: "2025-10-07",
+        last_updated: "2026-04-29",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.5, output: 3.5 },
+        limit: { context: 1000000, output: 32000 },
+      },
+      "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-31",
+        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-opus-4.7": {
+        id: "anthropic/claude-opus-4.7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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-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-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-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-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: 128000 },
+      },
+      "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-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": {
+        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-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.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.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-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-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 },
+      },
+      "xiaomi/mimo-v2.5-pro": {
+        id: "xiaomi/mimo-v2.5-pro",
+        name: "MiMo V2.5 Pro",
+        family: "mimo-v2.5-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-22",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 3, cache_read: 0.19999999999999998 },
+        limit: { context: 1050000, output: 131000 },
+      },
+      "xiaomi/mimo-v2.5": {
+        id: "xiaomi/mimo-v2.5",
+        name: "MiMo M2.5",
+        family: "mimo-v2.5",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-22",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.39999999999999997, output: 2, cache_read: 0.08 },
+        limit: { context: 1050000, output: 131100 },
+      },
+      "xiaomi/mimo-v2-pro": {
+        id: "xiaomi/mimo-v2-pro",
+        name: "MiMo V2 Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 3, cache_read: 0.19999999999999998 },
+        limit: { context: 1000000, output: 128000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-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-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 },
+      },
+    },
+  },
+  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": {
+        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": {
+        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.7": {
+        id: "MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06, cache_write: 0.375 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "MiniMax-M2.7-highspeed": {
+        id: "MiniMax-M2.7-highspeed",
+        name: "MiniMax-M2.7-highspeed",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        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 },
+      },
+      "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 },
+      },
+    },
+  },
+  llmgateway: {
+    id: "llmgateway",
+    env: ["LLMGATEWAY_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.llmgateway.io/v1",
+    name: "LLM Gateway",
+    doc: "https://llmgateway.io/docs",
+    models: {
+      "gpt-4o-mini-search-preview": {
+        id: "gpt-4o-mini-search-preview",
+        name: "GPT-4o Mini Search Preview",
+        family: "gpt",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        release_date: "2024-10-01",
+        last_updated: "2024-10-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.15, output: 0.6 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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,
+        structured_output: 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 },
+      },
+      "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,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-07-08",
+        last_updated: "2025-07-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.8, output: 2.4 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "llama-4-scout": {
+        id: "llama-4-scout",
+        name: "Llama 4 Scout",
+        family: "llama",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: false,
+        temperature: true,
+        release_date: "2025-04-05",
+        last_updated: "2025-04-05",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.18, output: 0.59 },
+        limit: { context: 32768, output: 16384 },
+        status: "beta",
+      },
+      "hermes-2-pro-llama-3-8b": {
+        id: "hermes-2-pro-llama-3-8b",
+        name: "Hermes 2 Pro Llama 3 8B",
+        family: "hermes",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        release_date: "2024-05-27",
+        last_updated: "2024-05-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.14 },
+        limit: { context: 8192, output: 8192 },
+      },
+      "qwen-coder-plus": {
+        id: "qwen-coder-plus",
+        name: "Qwen Coder Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2024-09-18",
+        last_updated: "2024-09-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 1 },
+        limit: { context: 131072, output: 8192 },
+      },
+      auto: {
+        id: "auto",
+        name: "Auto Route",
+        family: "auto",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2024-01-01",
+        last_updated: "2024-01-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "glm-4.6v-flashx": {
+        id: "glm-4.6v-flashx",
+        name: "GLM-4.6V FlashX",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-12-08",
+        last_updated: "2025-12-08",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.04, output: 0.4, cache_read: 0 },
+        limit: { context: 128000, output: 16000 },
+      },
+      "gemma-2-27b-it-together": {
+        id: "gemma-2-27b-it-together",
+        name: "Gemma 2 27B IT",
+        family: "gemma",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        release_date: "2024-06-27",
+        last_updated: "2024-06-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.08, output: 0.08 },
+        limit: { context: 8192, output: 16384 },
+      },
+      "codestral-2508": {
+        id: "codestral-2508",
+        name: "Codestral",
+        family: "mistral",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-07-30",
+        last_updated: "2025-07-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 0.9 },
+        limit: { context: 256000, output: 16384 },
+      },
+      "gemma-3-1b-it": {
+        id: "gemma-3-1b-it",
+        name: "Gemma 3 1B IT",
+        family: "gemma",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        release_date: "2025-03-12",
+        last_updated: "2025-03-12",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.08, output: 0.3 },
+        limit: { context: 1000000, output: 16384 },
+      },
+      "glm-4-32b-0414-128k": {
+        id: "glm-4-32b-0414-128k",
+        name: "GLM-4 32B (0414-128k)",
+        family: "glm",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-04-14",
+        last_updated: "2025-04-14",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "seed-1-6-flash-250715": {
+        id: "seed-1-6-flash-250715",
+        name: "Seed 1.6 Flash (250715)",
+        family: "seed",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-07-26",
+        last_updated: "2025-07-26",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.07, output: 0.3, cache_read: 0.01 },
+        limit: { context: 256000, output: 8192 },
+      },
+      "seed-1-6-250615": {
+        id: "seed-1-6-250615",
+        name: "Seed 1.6 (250615)",
+        family: "seed",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-06-25",
+        last_updated: "2025-06-25",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 2, cache_read: 0.05 },
+        limit: { context: 256000, output: 8192 },
+      },
+      "qwen3-vl-235b-a22b-thinking": {
+        id: "qwen3-vl-235b-a22b-thinking",
+        name: "Qwen3 VL 235B A22B Thinking",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-09-15",
+        last_updated: "2025-09-15",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.8, output: 2.4 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "qwen3-vl-30b-a3b-thinking": {
+        id: "qwen3-vl-30b-a3b-thinking",
+        name: "Qwen3 VL 30B A3B Thinking",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-10-02",
+        last_updated: "2025-10-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "qwen2-5-vl-32b-instruct": {
+        id: "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-15",
+        last_updated: "2025-03-15",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 0.3 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "qwen3-vl-8b-instruct": {
+        id: "qwen3-vl-8b-instruct",
+        name: "Qwen3 VL 8B Instruct",
+        family: "qwen",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        release_date: "2025-08-19",
+        last_updated: "2025-08-19",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "claude-3-7-sonnet": {
+        id: "claude-3-7-sonnet",
+        name: "Claude 3.7 Sonnet",
+        family: "claude",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: false,
+        temperature: true,
+        release_date: "2025-02-24",
+        last_updated: "2025-02-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15, cache_read: 0.3 },
+        limit: { context: 200000, output: 8192 },
+      },
+      "gemini-pro-latest": {
+        id: "gemini-pro-latest",
+        name: "Gemini Pro Latest",
+        family: "gemini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-02-27",
+        last_updated: "2026-02-27",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 12, cache_read: 0.2 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "claude-3-5-haiku": {
+        id: "claude-3-5-haiku",
+        name: "Claude 3.5 Haiku",
+        family: "claude",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: false,
+        temperature: true,
+        release_date: "2024-10-22",
+        last_updated: "2024-10-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.8, output: 4, cache_read: 0.08 },
+        limit: { context: 200000, output: 8192 },
+        status: "deprecated",
+      },
+      "qwen-max-latest": {
+        id: "qwen-max-latest",
+        name: "Qwen Max Latest",
+        family: "qwen",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-01-25",
+        last_updated: "2025-01-25",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.6, output: 6.4 },
+        limit: { context: 32768, output: 8192 },
+      },
+      "glm-4.6v-flash": {
+        id: "glm-4.6v-flash",
+        name: "GLM-4.6V Flash",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-12-08",
+        last_updated: "2025-12-08",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 128000, output: 16000 },
+        status: "beta",
+      },
+      "qwen3-30b-a3b-instruct-2507": {
+        id: "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-08",
+        last_updated: "2025-07-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "minimax-text-01": {
+        id: "minimax-text-01",
+        name: "MiniMax Text 01",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        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: 1000000, output: 131072 },
+      },
+      "qwen3-32b-fp8": {
+        id: "qwen3-32b-fp8",
+        name: "Qwen3 32B FP8",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-04-28",
+        last_updated: "2025-04-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "llama-4-scout-17b-instruct": {
+        id: "llama-4-scout-17b-instruct",
+        name: "Llama 4 Scout 17B Instruct",
+        family: "llama",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: 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.17, output: 0.66 },
+        limit: { context: 8192, output: 2048 },
+      },
+      "qwen3-4b-fp8": {
+        id: "qwen3-4b-fp8",
+        name: "Qwen3 4B FP8",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-04-28",
+        last_updated: "2025-04-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.03, output: 0.05 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "ministral-8b-2512": {
+        id: "ministral-8b-2512",
+        name: "Ministral 8B",
+        family: "mistral",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: 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.15, output: 0.15 },
+        limit: { context: 262144, output: 8192 },
+      },
+      "gemma-3-27b": {
+        id: "gemma-3-27b",
+        name: "Gemma 3 27B",
+        family: "gemma",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        release_date: "2025-03-12",
+        last_updated: "2025-03-12",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.27, output: 0.27 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "qwen3-vl-flash": {
+        id: "qwen3-vl-flash",
+        name: "Qwen3 VL Flash",
+        family: "qwen",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-10-09",
+        last_updated: "2025-10-09",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.05, output: 0.4, cache_read: 0.01 },
+        limit: { context: 1000000, output: 32000 },
+      },
+      "llama-3.1-70b-instruct": {
+        id: "llama-3.1-70b-instruct",
+        name: "Llama 3.1 70B Instruct",
+        family: "llama",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        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: 2048 },
+        status: "beta",
+      },
+      "seed-1-8-251228": {
+        id: "seed-1-8-251228",
+        name: "Seed 1.8 (251228)",
+        family: "seed",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-12-18",
+        last_updated: "2025-12-18",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 2, cache_read: 0.05 },
+        limit: { context: 256000, output: 8192 },
+      },
+      "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,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-07-08",
+        last_updated: "2025-07-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.8, output: 2.4 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "seed-1-6-250915": {
+        id: "seed-1-6-250915",
+        name: "Seed 1.6 (250915)",
+        family: "seed",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-09-15",
+        last_updated: "2025-09-15",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.25, output: 2, cache_read: 0.05 },
+        limit: { context: 256000, output: 8192 },
+      },
+      "glm-4.5-x": {
+        id: "glm-4.5-x",
+        name: "GLM-4.5 X",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-07-28",
+        last_updated: "2025-07-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2.2, output: 8.9, cache_read: 0.45 },
+        limit: { context: 128000, output: 16384 },
+        status: "beta",
+      },
+      "qwen3-30b-a3b-thinking-2507": {
+        id: "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-08",
+        last_updated: "2025-07-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "grok-4-fast-reasoning": {
+        id: "grok-4-fast-reasoning",
+        name: "Grok 4 Fast Reasoning",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: 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, cache_read: 0.05 },
+        limit: { context: 2000000, output: 30000 },
+      },
+      "deepseek-v3.1": {
+        id: "deepseek-v3.1",
+        name: "DeepSeek V3.1",
+        family: "deepseek",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-08-21",
+        last_updated: "2025-08-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.56, output: 1.68, cache_read: 0.11 },
+        limit: { context: 128000, output: 32768 },
+      },
+      "ministral-3b-2512": {
+        id: "ministral-3b-2512",
+        name: "Ministral 3B",
+        family: "mistral",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: 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: 131072, output: 8192 },
+      },
+      "qwen-plus-latest": {
+        id: "qwen-plus-latest",
+        name: "Qwen Plus Latest",
+        family: "qwen",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-01-25",
+        last_updated: "2025-01-25",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.3, output: 0.9 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "llama-3.1-nemotron-ultra-253b": {
+        id: "llama-3.1-nemotron-ultra-253b",
+        name: "Llama 3.1 Nemotron Ultra 253B",
+        family: "llama",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-04-07",
+        last_updated: "2025-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 1.8 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "llama-4-maverick-17b-instruct": {
+        id: "llama-4-maverick-17b-instruct",
+        name: "Llama 4 Maverick 17B Instruct",
+        family: "llama",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: 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.24, output: 0.97 },
+        limit: { context: 8192, output: 2048 },
+      },
+      "grok-4-0709": {
+        id: "grok-4-0709",
+        name: "Grok 4 (0709)",
+        family: "grok",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-07-09",
+        last_updated: "2025-07-09",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "qwen3-30b-a3b-fp8": {
+        id: "qwen3-30b-a3b-fp8",
+        name: "Qwen3 30B A3B FP8",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-04-28",
+        last_updated: "2025-04-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "minimax-m2.1-lightning": {
+        id: "minimax-m2.1-lightning",
+        name: "MiniMax M2.1 Lightning",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        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.12, output: 0.48 },
+        limit: { context: 196608, output: 131072 },
+      },
+      "qwen3-max-2026-01-23": {
+        id: "qwen3-max-2026-01-23",
+        name: "Qwen3 Max (2026-01-23)",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-01-23",
+        last_updated: "2026-01-23",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 3, output: 15, cache_read: 0.6 },
+        limit: { context: 256000, output: 32800 },
+      },
+      "llama-3.2-3b-instruct": {
+        id: "llama-3.2-3b-instruct",
+        name: "Llama 3.2 3B Instruct",
+        family: "llama",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2024-09-18",
+        last_updated: "2024-09-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.03, output: 0.05 },
+        limit: { context: 32768, output: 32000 },
+      },
+      "qwen3-coder-next": {
+        id: "qwen3-coder-next",
+        name: "Qwen3 Coder Next",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-10-15",
+        last_updated: "2025-10-15",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.8, output: 4 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "gpt-4o-search-preview": {
+        id: "gpt-4o-search-preview",
+        name: "GPT-4o Search Preview",
+        family: "gpt",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        release_date: "2024-10-01",
+        last_updated: "2024-10-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2.5, output: 10 },
+        limit: { context: 128000, output: 16384 },
+      },
+      custom: {
+        id: "custom",
+        name: "Custom Model",
+        family: "auto",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2024-01-01",
+        last_updated: "2024-01-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "qwen3-vl-30b-a3b-instruct": {
+        id: "qwen3-vl-30b-a3b-instruct",
+        name: "Qwen3 VL 30B A3B Instruct",
+        family: "qwen",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-10-02",
+        last_updated: "2025-10-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "deepseek-v3.2": {
+        id: "deepseek-v3.2",
+        name: "DeepSeek V3.2",
+        family: "deepseek",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-09-29",
+        last_updated: "2025-09-29",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.28, output: 0.42, cache_read: 0.03 },
+        limit: { context: 163840, output: 16384 },
+      },
+      "qwen3-235b-a22b-fp8": {
+        id: "qwen3-235b-a22b-fp8",
+        name: "Qwen3 235B A22B FP8",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-04-28",
+        last_updated: "2025-04-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 2.5 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "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: "2025-08-05",
+        last_updated: "2025-08-05",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.1, output: 0.5 },
+        limit: { context: 131072, output: 32766 },
+      },
+      "kimi-k2": {
+        id: "kimi-k2",
+        name: "Kimi K2",
+        family: "kimi",
+        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: 1, output: 3, cache_read: 0.5 },
+        limit: { context: 131072, output: 16384 },
+      },
+      "llama-3-8b-instruct": {
+        id: "llama-3-8b-instruct",
+        name: "Llama 3 8B Instruct",
+        family: "llama",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-04-03",
+        last_updated: "2025-04-03",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.04, output: 0.04 },
+        limit: { context: 8192, output: 8192 },
+      },
+      "qwen3-vl-235b-a22b-instruct": {
+        id: "qwen3-vl-235b-a22b-instruct",
+        name: "Qwen3 VL 235B A22B Instruct",
+        family: "qwen",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-09-15",
+        last_updated: "2025-09-15",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.8, output: 2.4 },
+        limit: { context: 131072, 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,
+        release_date: "2025-08-05",
+        last_updated: "2025-08-05",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.15, output: 0.75 },
+        limit: { context: 131072, output: 32766 },
+      },
+      "qwen25-coder-7b": {
+        id: "qwen25-coder-7b",
+        name: "Qwen2.5 Coder 7B",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2024-09-19",
+        last_updated: "2024-09-19",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.05, output: 0.05 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "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,
+        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.22, output: 0.22 },
+        limit: { context: 128000, output: 2048 },
+        status: "beta",
+      },
+      "llama-3-70b-instruct": {
+        id: "llama-3-70b-instruct",
+        name: "Llama 3 70B Instruct",
+        family: "llama",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2024-04-18",
+        last_updated: "2024-04-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.51, output: 0.74 },
+        limit: { context: 8192, output: 8000 },
+      },
+      "deepseek-r1-0528": {
+        id: "deepseek-r1-0528",
+        name: "DeepSeek R1 (0528)",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: false,
+        structured_output: false,
+        temperature: true,
+        release_date: "2025-05-28",
+        last_updated: "2025-05-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.8, output: 2.4 },
+        limit: { context: 64000, output: 16384 },
+        status: "beta",
+      },
+      "glm-4.5-airx": {
+        id: "glm-4.5-airx",
+        name: "GLM-4.5 AirX",
+        family: "glm",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-07-28",
+        last_updated: "2025-07-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.1, output: 4.5, cache_read: 0.22 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "ministral-14b-2512": {
+        id: "ministral-14b-2512",
+        name: "Ministral 14B",
+        family: "mistral",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        structured_output: 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.2, output: 0.2 },
+        limit: { context: 262144, output: 8192 },
+      },
+      "llama-3.2-11b-instruct": {
+        id: "llama-3.2-11b-instruct",
+        name: "Llama 3.2 11B Instruct",
+        family: "llama",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        structured_output: true,
+        temperature: true,
+        release_date: "2024-09-25",
+        last_updated: "2024-09-25",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.07, output: 0.33 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "claude-3-opus": {
+        id: "claude-3-opus",
+        name: "Claude 3 Opus",
+        family: "claude",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: false,
+        temperature: true,
+        release_date: "2024-03-04",
+        last_updated: "2024-03-04",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 15, output: 75, cache_read: 1.5 },
+        limit: { context: 200000, output: 4096 },
+      },
+      "minimax-m2.7": {
+        id: "minimax-m2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06, cache_write: 0.375 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "grok-4-20-beta-0309-non-reasoning": {
+        id: "grok-4-20-beta-0309-non-reasoning",
+        name: "Grok 4.20 (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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "kimi-k2.5": {
+        id: "kimi-k2.5",
+        name: "Kimi K2.5",
+        family: "kimi-k2.5",
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "minimax-m2.7-highspeed": {
+        id: "minimax-m2.7-highspeed",
+        name: "MiniMax-M2.7-highspeed",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        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 },
+      },
+      "mimo-v2.5-pro": {
+        id: "mimo-v2.5-pro",
+        name: "MiMo-V2.5-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "gemma-3n-e4b-it": {
+        id: "gemma-3n-e4b-it",
+        name: "Gemma 3n 4B",
+        family: "gemma",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2024-10",
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-31",
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "qwen35-397b-a17b": {
+        id: "qwen35-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-15",
+        last_updated: "2026-02-15",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3.6 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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 },
+      },
+      "gpt-5.3-chat-latest": {
+        id: "gpt-5.3-chat-latest",
+        name: "GPT-5.3 Chat (latest)",
+        family: "gpt",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "gpt-5.5": {
+        id: "gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+        experimental: {
+          modes: {
+            fast: {
+              cost: { input: 12.5, output: 75, cache_read: 1.25 },
+              provider: { body: { service_tier: "priority" } },
+            },
+          },
+        },
+      },
+      "qwen3.6-35b-a3b": {
+        id: "qwen3.6-35b-a3b",
+        name: "Qwen3.6 35B-A3B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-17",
+        last_updated: "2026-04-17",
+        modalities: { input: ["text", "image", "video", "audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.248, output: 1.485 },
+        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 },
+      },
+      "claude-opus-4-7": {
+        id: "claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+      },
+      "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-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 },
+      },
+      "mimo-v2-omni": {
+        id: "mimo-v2-omni",
+        name: "MiMo-V2-Omni",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 2, cache_read: 0.08 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.125,
+          context_over_200k: { input: 2.5, output: 15, cache_read: 0.25 },
+        },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "mimo-v2.5": {
+        id: "mimo-v2.5",
+        name: "MiMo-V2.5",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: {
+          input: 0.4,
+          output: 2,
+          cache_read: 0.08,
+          context_over_200k: { input: 0.8, output: 4, cache_read: 0.16 },
+        },
+        limit: { context: 1048576, 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,
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "grok-4-20-beta-0309-reasoning": {
+        id: "grok-4-20-beta-0309-reasoning",
+        name: "Grok 4.20 (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 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.15, output: 0.6, cache_read: 0.08 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "qwen3.6-plus": {
+        id: "qwen3.6-plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.276, output: 1.651, cache_read: 0.028, cache_write: 0.344 },
+        limit: { context: 1000000, output: 65536 },
+      },
+      "gpt-5.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "GPT-5.4 mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "glm-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 6, output: 24, cache_read: 1.3, cache_write: 0 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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-5.4-nano": {
+        id: "gpt-5.4-nano",
+        name: "GPT-5.4 nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "gemma-3-12b-it": {
+        id: "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, output: 0 },
+        limit: { context: 32768, 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: 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 },
+      },
+      "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.03, input_audio: 1 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "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 },
+      },
+      "gemma-3n-e2b-it": {
+        id: "gemma-3n-e2b-it",
+        name: "Gemma 3n 2B",
+        family: "gemma",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2024-10",
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "gemini-3.1-flash-lite": {
+        id: "gemini-3.1-flash-lite",
+        name: "Gemini 3.1 Flash Lite",
+        family: "gemini-flash-lite",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-05-07",
+        last_updated: "2026-05-07",
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "gemma-3-4b-it": {
+        id: "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, output: 0 },
+        limit: { context: 32768, output: 8192 },
+      },
+      "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 },
+      },
+      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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 15, output: 60, cache_read: 7.5 },
+        limit: { context: 200000, output: 100000 },
+      },
+      "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 },
+      },
+      "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: 1050000, input: 922000, output: 128000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "grok-4-3": {
+        id: "grok-4-3",
+        name: "Grok 4.3",
+        family: "grok",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-05-01",
+        last_updated: "2026-05-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: {
+          input: 1.25,
+          output: 2.5,
+          cache_read: 0.2,
+          context_over_200k: { input: 2.5, output: 5, cache_read: 0.4 },
+        },
+        limit: { context: 1000000, output: 30000 },
+      },
+      "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 },
+      },
+      "deepseek-v4-flash": {
+        id: "deepseek-v4-flash",
+        name: "DeepSeek V4 Flash",
+        family: "deepseek-flash",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.28, cache_read: 0.028 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "deepseek-v4-pro": {
+        id: "deepseek-v4-pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-05",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.74, output: 3.48, cache_read: 0.145 },
+        limit: { context: 1000000, output: 384000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "mimo-v2-pro": {
+        id: "mimo-v2-pro",
+        name: "MiMo-V2-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 8, cache_read: 0.5 },
+        limit: { context: 200000, output: 100000 },
+      },
+      "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": {
+        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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2.5, output: 10, cache_read: 1.25 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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: 262144, output: 65536 },
+      },
+      "qwen3.6-max-preview": {
+        id: "qwen3.6-max-preview",
+        name: "Qwen3.6 Max Preview",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-04",
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1.3, output: 7.8, cache_read: 0.13, cache_write: 1.625 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "gpt-5.5-pro": {
+        id: "gpt-5.5-pro",
+        name: "GPT-5.5 Pro",
+        family: "gpt-pro",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-23",
+        last_updated: "2026-04-23",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 30, output: 180, context_over_200k: { input: 60, output: 270 } },
+        limit: { context: 1050000, input: 922000, 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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 8, cache_read: 0.5 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 1.6, cache_read: 0.1 },
+        limit: { context: 1047576, output: 32768 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "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-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 },
+      },
+      "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-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-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-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 },
+      },
+      "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.2-maas": {
+        id: "deepseek-ai/deepseek-v3.2-maas",
+        name: "DeepSeek V3.2",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2025-12-17",
+        last_updated: "2026-04-04",
+        modalities: { input: ["text", "pdf"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.56, output: 1.68, cache_read: 0.056 },
+        limit: { context: 163840, output: 65536 },
+        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",
+        },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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",
+        },
+      },
+      "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",
+        },
+      },
+      "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",
+        },
+      },
+      "moonshotai/kimi-k2-thinking-maas": {
+        id: "moonshotai/kimi-k2-thinking-maas",
+        name: "Kimi K2 Thinking",
+        family: "kimi-thinking",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2024-08",
+        release_date: "2025-11-13",
+        last_updated: "2025-11-13",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 2.5 },
+        limit: { context: 262144, output: 262144 },
+        provider: {
+          npm: "@ai-sdk/openai-compatible",
+          api: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi",
+        },
+      },
+      "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-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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+      },
+      "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-31",
+        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,
+          context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 },
+        },
+        limit: { context: 200000, output: 64000 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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-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-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-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.125,
+          context_over_200k: { input: 2.5, output: 15, cache_read: 0.25 },
+        },
+        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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "gemini-3.1-flash-lite": {
+        id: "gemini-3.1-flash-lite",
+        name: "Gemini 3.1 Flash Lite",
+        family: "gemini-flash-lite",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-05-07",
+        last_updated: "2026-05-07",
+        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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+      },
+      "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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+      },
+      "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-31",
+        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,
+          context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 },
+        },
+        limit: { context: 1000000, output: 128000 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+      "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 },
+      },
+      "claude-opus-4-7@default": {
+        id: "claude-opus-4-7@default",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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 },
+        provider: { npm: "@ai-sdk/google-vertex/anthropic" },
+      },
+    },
+  },
+  "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/nvidia/nemotron-3-120b-a12b": {
+        id: "@cf/nvidia/nemotron-3-120b-a12b",
+        name: "Nemotron 3 Super 120B",
+        family: "nemotron",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-03-11",
+        last_updated: "2026-03-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 1.5 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "@cf/openai/gpt-oss-20b": {
+        id: "@cf/openai/gpt-oss-20b",
+        name: "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.2, output: 0.3 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "@cf/openai/gpt-oss-120b": {
+        id: "@cf/openai/gpt-oss-120b",
+        name: "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.35, output: 0.75 },
+        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: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-04-16",
+        last_updated: "2025-04-16",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.27, output: 0.85 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "@cf/google/gemma-4-26b-a4b-it": {
+        id: "@cf/google/gemma-4-26b-a4b-it",
+        name: "Gemma 4 26B A4B IT",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-12-15",
+        last_updated: "2025-12-15",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.3 },
+        limit: { context: 256000, output: 16384 },
+      },
+      "@cf/moonshotai/kimi-k2.5": {
+        id: "@cf/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"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.6, output: 3, cache_read: 0.1 },
+        limit: { context: 256000, output: 256000 },
+      },
+      "@cf/moonshotai/kimi-k2.6": {
+        id: "@cf/moonshotai/kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        limit: { context: 256000, output: 256000 },
+      },
+    },
+  },
+  groq: {
+    id: "groq",
+    env: ["GROQ_API_KEY"],
+    npm: "@ai-sdk/groq",
+    name: "Groq",
+    doc: "https://console.groq.com/docs/models",
+    models: {
+      "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",
+      },
+      "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",
+      },
+      "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",
+      },
+      "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",
+      },
+      "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 },
+      },
+      "allam-2-7b": {
+        id: "allam-2-7b",
+        name: "ALLaM-2-7b",
+        family: "allam",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2024-09",
+        release_date: "2024-09",
+        last_updated: "2024-09",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 4096, output: 4096 },
+      },
+      "whisper-large-v3": {
+        id: "whisper-large-v3",
+        name: "Whisper Large V3",
+        family: "whisper",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        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: 448, output: 448 },
+      },
+      "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 },
+      },
+      "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",
+      },
+      "whisper-large-v3-turbo": {
+        id: "whisper-large-v3-turbo",
+        name: "Whisper Large v3 Turbo",
+        family: "whisper",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2024-10",
+        release_date: "2024-10-01",
+        last_updated: "2024-10-01",
+        modalities: { input: ["audio"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 448, output: 448 },
+      },
+      "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",
+      },
+      "canopylabs/orpheus-arabic-saudi": {
+        id: "canopylabs/orpheus-arabic-saudi",
+        name: "Orpheus Arabic Saudi",
+        family: "canopylabs",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2025-12-16",
+        release_date: "2025-12-16",
+        last_updated: "2025-12-16",
+        modalities: { input: ["text"], output: ["audio"] },
+        open_weights: false,
+        cost: { input: 40, output: 0 },
+        limit: { context: 4000, output: 50000 },
+      },
+      "canopylabs/orpheus-v1-english": {
+        id: "canopylabs/orpheus-v1-english",
+        name: "Orpheus V1 English",
+        family: "canopylabs",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2025-12-19",
+        release_date: "2025-12-19",
+        last_updated: "2025-12-19",
+        modalities: { input: ["text"], output: ["audio"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 4000, output: 50000 },
+      },
+      "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-prompt-guard-2-22m": {
+        id: "meta-llama/llama-prompt-guard-2-22m",
+        name: "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: true,
+        cost: { input: 0.03, output: 0.03 },
+        limit: { context: 512, output: 512 },
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "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 },
+        status: "deprecated",
+      },
+      "meta-llama/llama-prompt-guard-2-86m": {
+        id: "meta-llama/llama-prompt-guard-2-86m",
+        name: "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: true,
+        cost: { input: 0.04, output: 0.04 },
+        limit: { context: 512, output: 512 },
+      },
+      "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 },
+      },
+      "openai/gpt-oss-safeguard-20b": {
+        id: "openai/gpt-oss-safeguard-20b",
+        name: "Safety GPT OSS 20B",
+        family: "gpt-oss",
+        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.075, output: 0.3, cache_read: 0.037 },
+        limit: { context: 131072, output: 65536 },
+      },
+      "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 },
+      },
+      "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: 40960 },
+      },
+      "groq/compound": {
+        id: "groq/compound",
+        name: "Compound",
+        family: "groq",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-09-04",
+        release_date: "2025-09-04",
+        last_updated: "2025-09-04",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "groq/compound-mini": {
+        id: "groq/compound-mini",
+        name: "Compound Mini",
+        family: "groq",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-09-04",
+        release_date: "2025-09-04",
+        last_updated: "2025-09-04",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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-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 },
+      },
+      "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-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.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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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",
+        },
+      },
+      "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 },
+      },
+      "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",
+        },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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",
+      },
+      "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-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 },
+      },
+      "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-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",
+        },
+      },
+      "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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "grok-4-20-non-reasoning": {
+        id: "grok-4-20-non-reasoning",
+        name: "Grok 4.20 (Non-Reasoning)",
+        family: "grok",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-09",
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6 },
+        limit: { context: 262000, output: 8192 },
+        status: "beta",
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "kimi-k2.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4 },
+        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-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 },
+      },
+      "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 },
+      },
+      "grok-4-20-reasoning": {
+        id: "grok-4-20-reasoning",
+        name: "Grok 4.20 (Reasoning)",
+        family: "grok",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-09",
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 2, output: 6 },
+        limit: { context: 262000, output: 8192 },
+        status: "beta",
+      },
+      "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 },
+      },
+      "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,
+          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",
+        },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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",
+        },
+      },
+      "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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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,
+        knowledge: "2025-08-31",
+        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 },
+        provider: {
+          npm: "@ai-sdk/anthropic",
+          api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1",
+        },
+      },
+      "gpt-5.5": {
+        id: "gpt-5.5",
+        name: "GPT-5.5",
+        family: "gpt",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-12-01",
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 30, cache_read: 0.5, context_over_200k: { input: 10, output: 45, cache_read: 1 } },
+        limit: { context: 1050000, input: 922000, 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-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-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 },
+      },
+      "gpt-5.4-mini": {
+        id: "gpt-5.4-mini",
+        name: "GPT-5.4 Mini",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 4.5, cache_read: 0.075 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "cohere-command-r-08-2024": {
+        id: "cohere-command-r-08-2024",
+        name: "Command R",
+        family: "command-r",
+        attachment: false,
+        reasoning: false,
+        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 },
+      },
+      "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.4-nano": {
+        id: "gpt-5.4-nano",
+        name: "GPT-5.4 Nano",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.2, output: 1.25, cache_read: 0.02 },
+        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: "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 },
+      },
+      "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 },
+      },
+      "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": {
+        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.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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      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 },
+      },
+      "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-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 },
+      },
+      "cohere-command-r-plus-08-2024": {
+        id: "cohere-command-r-plus-08-2024",
+        name: "Command R+",
+        family: "command-r",
+        attachment: false,
+        reasoning: false,
+        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-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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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": {
+        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-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 },
+      },
+      "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" },
+        temperature: true,
+        release_date: "2026-02-11",
+        last_updated: "2026-02-11",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 3.15 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "tencent-coding-plan": {
+    id: "tencent-coding-plan",
+    env: ["TENCENT_CODING_PLAN_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.lkeap.cloud.tencent.com/coding/v3",
+    name: "Tencent Coding Plan (China)",
+    doc: "https://cloud.tencent.com/document/product/1772/128947",
+    models: {
+      "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 },
+      },
+      "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 },
+      },
+      "hunyuan-turbos": {
+        id: "hunyuan-turbos",
+        name: "Hunyuan-TurboS",
+        family: "hunyuan",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-08",
+        last_updated: "2026-03-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 131072, output: 16384 },
+      },
+      "hunyuan-t1": {
+        id: "hunyuan-t1",
+        name: "Hunyuan-T1",
+        family: "hunyuan",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-03-08",
+        last_updated: "2026-03-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 131072, output: 16384 },
+      },
+      "hunyuan-2.0-instruct": {
+        id: "hunyuan-2.0-instruct",
+        name: "Tencent HY 2.0 Instruct",
+        family: "hunyuan",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-08",
+        last_updated: "2026-03-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 131072, 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: 204800, output: 32768 },
+      },
+      "tc-code-latest": {
+        id: "tc-code-latest",
+        name: "Auto",
+        family: "auto",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-08",
+        last_updated: "2026-03-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 131072, output: 16384 },
+      },
+      "hunyuan-2.0-thinking": {
+        id: "hunyuan-2.0-thinking",
+        name: "Tencent HY 2.0 Think",
+        family: "hunyuan",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-03-08",
+        last_updated: "2026-03-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 131072, output: 16384 },
+      },
+    },
+  },
+  "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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/models",
+    models: {
+      "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-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-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-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-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-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-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 },
+      },
+      "gemma-3n-e4b-it": {
+        id: "gemma-3n-e4b-it",
+        name: "Gemma 3n 4B",
+        family: "gemma",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2024-10",
+        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 },
+      },
+      "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-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-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-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-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-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.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-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-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.125,
+          context_over_200k: { input: 2.5, output: 15, cache_read: 0.25 },
+        },
+        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 },
+      },
+      "gemma-4-31b-it": {
+        id: "gemma-4-31b-it",
+        name: "Gemma 4 31B",
+        family: "gemma",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 256000, output: 8192 },
+      },
+      "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-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-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 },
+      },
+      "gemma-3-12b-it": {
+        id: "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, output: 0 },
+        limit: { context: 32768, output: 8192 },
+      },
+      "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.03, input_audio: 1 },
+        limit: { context: 1048576, output: 65536 },
+      },
+      "gemma-3n-e2b-it": {
+        id: "gemma-3n-e2b-it",
+        name: "Gemma 3n 2B",
+        family: "gemma",
+        attachment: true,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        knowledge: "2024-10",
+        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 },
+      },
+      "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": {
+        id: "gemini-3.1-flash-lite",
+        name: "Gemini 3.1 Flash Lite",
+        family: "gemini-flash-lite",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-05-07",
+        last_updated: "2026-05-07",
+        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 },
+      },
+      "gemma-3-4b-it": {
+        id: "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, output: 0 },
+        limit: { context: 32768, output: 8192 },
+      },
+      "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-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-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 },
+      },
+      "gemma-3-27b-it": {
+        id: "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, output: 0 },
+        limit: { context: 131072, output: 8192 },
+      },
+      "gemma-4-26b-a4b-it": {
+        id: "gemma-4-26b-a4b-it",
+        name: "Gemma 4 26B",
+        family: "gemma",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        limit: { context: 256000, output: 8192 },
+      },
+      "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-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-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-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 },
+      },
+    },
+  },
+  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-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 },
+      },
+      "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 },
+      },
+    },
+  },
+  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-k2.5",
+        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-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.6": {
+        id: "kimi-k2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4, cache_read: 0.16 },
+        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-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": {
+        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 },
+      },
+    },
+  },
+  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.77, output: 2.75 },
+        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.33, output: 0.33 },
+        limit: { context: 32000, output: 8192 },
+      },
+      "mistralai/Mistral-Medium-3.5-128B": {
+        id: "mistralai/Mistral-Medium-3.5-128B",
+        name: "Mistral Medium 3.5 128B",
+        family: "mistral-medium",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2026-04",
+        release_date: "2026-04-29",
+        last_updated: "2026-04-29",
+        modalities: { input: ["image", "text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.65, output: 5.5 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "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.99, output: 0.99 },
+        limit: { context: 128000, 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.44, output: 0.99 },
+        limit: { context: 128000, output: 8192 },
+      },
+      "google/gemma-4-31B-it": {
+        id: "google/gemma-4-31B-it",
+        name: "Gemma 4 31B Instruct",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-12",
+        release_date: "2026-04-02",
+        last_updated: "2026-04-02",
+        modalities: { input: ["audio", "image", "text", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.275, output: 0.55 },
+        limit: { context: 128000, output: 8192 },
+      },
+    },
+  },
+  "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: {
+      "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 },
+      },
+      "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-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 },
+      },
+      "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-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-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.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-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-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-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 },
+      },
+      "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-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-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/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-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/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 },
+      },
+      "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: false,
+        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-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": {
+        id: "cohere/cohere-command-r-plus",
+        name: "Cohere Command R+",
+        family: "command-r",
+        attachment: false,
+        reasoning: false,
+        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 },
+      },
+      "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-plus-08-2024": {
+        id: "cohere/cohere-command-r-plus-08-2024",
+        name: "Cohere Command R+ 08-2024",
+        family: "command-r",
+        attachment: false,
+        reasoning: false,
+        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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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/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/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/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-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/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/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-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 },
+      },
+      "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/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 },
+      },
+      "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-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-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/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/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.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.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.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-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-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 },
+      },
+      "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/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/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-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/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/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 },
+      },
+    },
+  },
+  neuralwatt: {
+    id: "neuralwatt",
+    env: ["NEURALWATT_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.neuralwatt.com/v1",
+    name: "Neuralwatt",
+    doc: "https://portal.neuralwatt.com/docs",
+    models: {
+      "glm-5-fast": {
+        id: "glm-5-fast",
+        name: "GLM 5 Fast",
+        family: "glm",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.1, output: 3.6 },
+        limit: { context: 200000, output: 200000 },
+      },
+      "kimi-k2.6-fast": {
+        id: "kimi-k2.6-fast",
+        name: "Kimi K2.6 Fast",
+        family: "kimi",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.69, output: 3.22 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "qwen3.5-397b-fast": {
+        id: "qwen3.5-397b-fast",
+        name: "Qwen3.5 397B Fast",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-02-01",
+        last_updated: "2026-02-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.69, output: 4.14 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "glm-5.1-fast": {
+        id: "glm-5.1-fast",
+        name: "GLM 5.1 Fast",
+        family: "glm",
+        attachment: false,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.1, output: 3.6 },
+        limit: { context: 200000, output: 200000 },
+      },
+      "qwen3.6-35b-fast": {
+        id: "qwen3.6-35b-fast",
+        name: "Qwen3.6 35B Fast",
+        family: "qwen3.6",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.05, output: 0.1 },
+        limit: { context: 131072, output: 131072 },
+      },
+      "kimi-k2.5-fast": {
+        id: "kimi-k2.5-fast",
+        name: "Kimi K2.5 Fast",
+        family: "kimi",
+        attachment: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-01-27",
+        last_updated: "2026-01-27",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.52, output: 2.59 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "Qwen/Qwen3.5-397B-A17B-FP8": {
+        id: "Qwen/Qwen3.5-397B-A17B-FP8",
+        name: "Qwen3.5 397B A17B FP8",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-02-01",
+        last_updated: "2026-02-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.69, output: 4.14 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "Qwen/Qwen3.6-35B-A3B": {
+        id: "Qwen/Qwen3.6-35B-A3B",
+        name: "Qwen3.6 35B A3B",
+        family: "qwen3.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.05, output: 0.1 },
+        limit: { context: 131072, output: 131072 },
+      },
+      "zai-org/GLM-5.1-FP8": {
+        id: "zai-org/GLM-5.1-FP8",
+        name: "GLM 5.1 FP8",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.1, output: 3.6 },
+        limit: { context: 200000, output: 200000 },
+      },
+      "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: true,
+        reasoning: false,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-12-09",
+        last_updated: "2025-12-09",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.12, output: 0.35 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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.16 },
+        limit: { context: 16384, output: 16384 },
+      },
+      "moonshotai/Kimi-K2.6": {
+        id: "moonshotai/Kimi-K2.6",
+        name: "Kimi K2.6",
+        family: "kimi",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.69, output: 3.22 },
+        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,
+        release_date: "2026-01-27",
+        last_updated: "2026-01-27",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.52, output: 2.59 },
+        limit: { context: 262144, output: 262144 },
+      },
+      "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.35, output: 1.38 },
+        limit: { context: 196608, output: 196608 },
+      },
+    },
+  },
+  togetherai: {
+    id: "togetherai",
+    env: ["TOGETHER_API_KEY"],
+    npm: "@ai-sdk/togetherai",
+    name: "Together AI",
+    doc: "https://docs.together.ai/docs/serverless-models",
+    models: {
+      "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 },
+      },
+      "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.6-Plus": {
+        id: "Qwen/Qwen3.6-Plus",
+        name: "Qwen3.6 Plus",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-30",
+        last_updated: "2026-04-30",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.5, output: 3 },
+        limit: { context: 1000000, output: 500000 },
+      },
+      "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-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-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 },
+      },
+      "zai-org/GLM-5.1": {
+        id: "zai-org/GLM-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-11",
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.4, output: 4.4 },
+        limit: { context: 202752, output: 131072 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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-V4-Pro": {
+        id: "deepseek-ai/DeepSeek-V4-Pro",
+        name: "DeepSeek V4 Pro",
+        family: "deepseek",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-24",
+        last_updated: "2026-04-24",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 2.1, output: 4.4, cache_read: 0.2 },
+        limit: { context: 512000, output: 384000 },
+      },
+      "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 },
+      },
+      "google/gemma-4-31B-it": {
+        id: "google/gemma-4-31B-it",
+        name: "Gemma 4 31B Instruct",
+        family: "gemma",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-07",
+        last_updated: "2026-04-07",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.2, output: 0.5 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "moonshotai/Kimi-K2.6": {
+        id: "moonshotai/Kimi-K2.6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1.2, output: 4.5, cache_read: 0.2 },
+        limit: { context: 262144, output: 131000 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "MiniMaxAI/MiniMax-M2.7": {
+        id: "MiniMaxAI/MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06 },
+        limit: { context: 202752, output: 131072 },
+      },
+    },
+  },
+  "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "tencent-tokenhub": {
+    id: "tencent-tokenhub",
+    env: ["TENCENT_TOKENHUB_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://tokenhub.tencentmaas.com/v1",
+    name: "Tencent TokenHub",
+    doc: "https://cloud.tencent.com/document/product/1823/130050",
+    models: {
+      "hy3-preview": {
+        id: "hy3-preview",
+        name: "Hy3 preview",
+        family: "Hy",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-04-20",
+        last_updated: "2026-04-20",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
+        limit: { context: 256000, output: 64000 },
+      },
+    },
+  },
+  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-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-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-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-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-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-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-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-31",
+        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-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-opus-4-7": {
+        id: "claude-opus-4-7",
+        name: "Claude Opus 4.7",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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-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-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-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-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-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-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-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-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-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-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-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 },
+        experimental: {
+          modes: {
+            fast: {
+              cost: { input: 30, output: 150, cache_read: 3, cache_write: 37.5 },
+              provider: { body: { speed: "fast" }, headers: { "anthropic-beta": "fast-mode-2026-02-01" } },
+            },
+          },
+        },
+      },
+      "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-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-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 },
+      },
+    },
+  },
+  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-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-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-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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "hpc-ai": {
+    id: "hpc-ai",
+    env: ["HPC_AI_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.hpc-ai.com/inference/v1",
+    name: "HPC-AI",
+    doc: "https://www.hpc-ai.com/doc/docs/quickstart/",
+    models: {
+      "zai-org/glm-5.1": {
+        id: "zai-org/glm-5.1",
+        name: "GLM 5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-04-08",
+        last_updated: "2026-04-08",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.66, output: 2, cache_read: 0.12 },
+        limit: { context: 202000, output: 202000 },
+      },
+      "minimax/minimax-m2.5": {
+        id: "minimax/minimax-m2.5",
+        name: "MiniMax M2.5",
+        family: "minimax-m2.5",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        structured_output: false,
+        temperature: true,
+        release_date: "2026-02-12",
+        last_updated: "2026-03-25",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.14, output: 0.56, cache_read: 0.014 },
+        limit: { context: 1000000, 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: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-01-01",
+        release_date: "2026-01-01",
+        last_updated: "2026-03-25",
+        modalities: { input: ["text", "image", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.21, output: 1, cache_read: 0.03 },
+        limit: { context: 262144, output: 262144 },
+      },
+    },
+  },
+  gitlab: {
+    id: "gitlab",
+    env: ["GITLAB_TOKEN"],
+    npm: "gitlab-ai-provider",
+    name: "GitLab Duo",
+    doc: "https://docs.gitlab.com/user/duo_agent_platform/",
+    models: {
+      "duo-chat-gpt-5-4-nano": {
+        id: "duo-chat-gpt-5-4-nano",
+        name: "Agentic Chat (GPT-5.4 Nano)",
+        family: "gpt-nano",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        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-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-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-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-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-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 },
+      },
+      "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-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-gpt-5-4": {
+        id: "duo-chat-gpt-5-4",
+        name: "Agentic Chat (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: 0, output: 0 },
+        limit: { context: 1050000, input: 922000, output: 128000 },
+      },
+      "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-3-codex": {
+        id: "duo-chat-gpt-5-3-codex",
+        name: "Agentic Chat (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: 0, output: 0 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "duo-chat-gpt-5-4-mini": {
+        id: "duo-chat-gpt-5-4-mini",
+        name: "Agentic Chat (GPT-5.4 Mini)",
+        family: "gpt-mini",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        structured_output: true,
+        temperature: false,
+        knowledge: "2025-08-31",
+        release_date: "2026-03-17",
+        last_updated: "2026-03-17",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0 },
+        limit: { context: 400000, input: 272000, output: 128000 },
+      },
+      "duo-chat-opus-4-7": {
+        id: "duo-chat-opus-4-7",
+        name: "Agentic Chat (Claude Opus 4.7)",
+        family: "claude-opus",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: false,
+        knowledge: "2026-01-31",
+        release_date: "2026-04-16",
+        last_updated: "2026-04-16",
+        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-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-05-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 },
+      },
+    },
+  },
+  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.5-pro": {
+        id: "mimo-v2.5-pro",
+        name: "MiMo-V2.5-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "mimo-v2-omni": {
+        id: "mimo-v2-omni",
+        name: "MiMo-V2-Omni",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.4, output: 2, cache_read: 0.08 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "mimo-v2.5": {
+        id: "mimo-v2.5",
+        name: "MiMo-V2.5",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: {
+          input: 0.4,
+          output: 2,
+          cache_read: 0.08,
+          context_over_200k: { input: 0.8, output: 4, cache_read: 0.16 },
+        },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "mimo-v2-pro": {
+        id: "mimo-v2-pro",
+        name: "MiMo-V2-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 1, output: 3, cache_read: 0.2, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "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: 262144, output: 65536 },
+      },
+    },
+  },
+  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: {
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "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-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 },
+      },
+      "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 },
+      },
+      "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 },
+      },
+      "moonshotai/chat-completion/models/Kimi-K2_6": {
+        id: "moonshotai/chat-completion/models/Kimi-K2_6",
+        name: "Kimi K2.6",
+        family: "kimi-k2.6",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        knowledge: "2025-01",
+        release_date: "2026-04-21",
+        last_updated: "2026-04-21",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.95, output: 4 },
+        limit: { context: 262144, output: 262144 },
+      },
+    },
+  },
+  "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": {
+        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": {
+        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.7": {
+        id: "MiniMax-M2.7",
+        name: "MiniMax-M2.7",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2, cache_read: 0.06, cache_write: 0.375 },
+        limit: { context: 204800, output: 131072 },
+      },
+      "MiniMax-M2.7-highspeed": {
+        id: "MiniMax-M2.7-highspeed",
+        name: "MiniMax-M2.7-highspeed",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        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 },
+      },
+      "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 },
+      },
+    },
+  },
+  "regolo-ai": {
+    id: "regolo-ai",
+    env: ["REGOLO_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://api.regolo.ai/v1",
+    name: "Regolo AI",
+    doc: "https://docs.regolo.ai/",
+    models: {
+      "mistral-small3.2": {
+        id: "mistral-small3.2",
+        name: "Mistral Small 3.2",
+        family: "mistral-small",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2025-01-31",
+        last_updated: "2025-01-31",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 2.2 },
+        limit: { context: 120000, output: 120000 },
+      },
+      "qwen3-embedding-8b": {
+        id: "qwen3-embedding-8b",
+        name: "Qwen3-Embedding-8B",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2026-02-01",
+        last_updated: "2026-02-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.1, output: 0.1 },
+        limit: { context: 32768, output: 8192 },
+      },
+      "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,
+        release_date: "2025-04-28",
+        last_updated: "2025-04-28",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.6, output: 2.7 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "qwen3-reranker-4b": {
+        id: "qwen3-reranker-4b",
+        name: "Qwen3-Reranker-4B",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: false,
+        release_date: "2026-02-01",
+        last_updated: "2026-02-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.12, output: 0.12 },
+        limit: { context: 32768, output: 8192 },
+      },
+      "mistral-small-4-119b": {
+        id: "mistral-small-4-119b",
+        name: "Mistral Small 4 119B",
+        family: "mistral-small",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-15",
+        last_updated: "2026-03-15",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.75, output: 3 },
+        limit: { context: 256000, output: 16384 },
+      },
+      "qwen3.5-122b": {
+        id: "qwen3.5-122b",
+        name: "Qwen3.5-122B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-02-01",
+        last_updated: "2026-02-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.9, output: 3.6 },
+        limit: { context: 262144, output: 16384 },
+      },
+      "qwen-image": {
+        id: "qwen-image",
+        name: "Qwen-Image",
+        family: "qwen",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        temperature: true,
+        release_date: "2026-03-01",
+        last_updated: "2026-03-01",
+        modalities: { input: ["text"], output: ["image"] },
+        open_weights: false,
+        cost: { input: 0.5, output: 2 },
+        limit: { context: 8192, output: 4096 },
+      },
+      "qwen3-coder-next": {
+        id: "qwen3-coder-next",
+        name: "Qwen3-Coder-Next",
+        family: "qwen",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-01",
+        last_updated: "2026-03-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.3, output: 1.2 },
+        limit: { context: 262144, output: 16384 },
+      },
+      "minimax-m2.5": {
+        id: "minimax-m2.5",
+        name: "MiniMax 2.5",
+        family: "minimax",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-10",
+        last_updated: "2026-03-10",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.8, output: 3.5 },
+        limit: { context: 190000, output: 64000 },
+      },
+      "gpt-oss-20b": {
+        id: "gpt-oss-20b",
+        name: "GPT-OSS-20B",
+        family: "gpt-oss",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-03-01",
+        last_updated: "2026-03-01",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.4, output: 1.8 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "qwen3.5-9b": {
+        id: "qwen3.5-9b",
+        name: "Qwen3.5-9B",
+        family: "qwen",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        temperature: true,
+        release_date: "2026-02-01",
+        last_updated: "2026-02-01",
+        modalities: { input: ["text", "image"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0.15, output: 0.6 },
+        limit: { context: 262144, output: 8192 },
+      },
+      "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: false,
+        cost: { input: 1, output: 4.2 },
+        limit: { context: 128000, output: 16384 },
+      },
+      "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,
+        release_date: "2025-04-07",
+        last_updated: "2025-04-07",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0.05, output: 0.25 },
+        limit: { context: 120000, output: 120000 },
+      },
+    },
+  },
+  "xiaomi-token-plan-ams": {
+    id: "xiaomi-token-plan-ams",
+    env: ["XIAOMI_API_KEY"],
+    npm: "@ai-sdk/openai-compatible",
+    api: "https://token-plan-ams.xiaomimimo.com/v1",
+    name: "Xiaomi Token Plan (Europe)",
+    doc: "https://platform.xiaomimimo.com/#/docs",
+    models: {
+      "mimo-v2-tts": {
+        id: "mimo-v2-tts",
+        name: "MiMo-V2-TTS",
+        family: "mimo",
+        attachment: false,
+        reasoning: false,
+        tool_call: false,
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["audio"] },
+        open_weights: true,
+        cost: { input: 0, output: 0 },
+        limit: { context: 8192, output: 16384 },
+      },
+      "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, output: 0, cache_read: 0 },
+        limit: { context: 262144, output: 65536 },
+      },
+      "mimo-v2-pro": {
+        id: "mimo-v2-pro",
+        name: "MiMo-V2-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 2, output: 6, cache_read: 0.4 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "mimo-v2.5": {
+        id: "mimo-v2.5",
+        name: "MiMo-V2.5",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 0, output: 0, cache_read: 0 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+      "mimo-v2-omni": {
+        id: "mimo-v2-omni",
+        name: "MiMo-V2-Omni",
+        family: "mimo",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-03-18",
+        last_updated: "2026-03-18",
+        modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 0, output: 0, cache_read: 0 },
+        limit: { context: 262144, output: 131072 },
+      },
+      "mimo-v2.5-pro": {
+        id: "mimo-v2.5-pro",
+        name: "MiMo-V2.5-Pro",
+        family: "mimo",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        knowledge: "2024-12",
+        release_date: "2026-04-22",
+        last_updated: "2026-04-22",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: true,
+        cost: { input: 0, output: 0, cache_read: 0, context_over_200k: { input: 0, output: 0, cache_read: 0 } },
+        limit: { context: 1048576, output: 131072 },
+      },
+    },
+  },
+  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-5v-turbo": {
+        id: "glm-5v-turbo",
+        name: "GLM-5V-Turbo",
+        family: "glm",
+        attachment: true,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        temperature: true,
+        release_date: "2026-04-01",
+        last_updated: "2026-04-01",
+        modalities: { input: ["text", "image", "video", "pdf"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 5, output: 22, cache_read: 1.2, cache_write: 0 },
+        limit: { context: 200000, output: 131072 },
+      },
+      "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-5.1": {
+        id: "glm-5.1",
+        name: "GLM-5.1",
+        family: "glm",
+        attachment: false,
+        reasoning: true,
+        tool_call: true,
+        interleaved: { field: "reasoning_content" },
+        structured_output: true,
+        temperature: true,
+        release_date: "2026-03-27",
+        last_updated: "2026-03-27",
+        modalities: { input: ["text"], output: ["text"] },
+        open_weights: false,
+        cost: { input: 6, output: 24, cache_read: 1.3, cache_write: 0 },
+        limit: { context: 200000, 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.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.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.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.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.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.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 },
+      },
+    },
+  },
+  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 },
+      },
+    },
+  },
+}
diff --git a/packages/core/src/plugin/boot.ts b/packages/core/src/plugin/boot.ts
index d3ea5195c..74560ac85 100644
--- a/packages/core/src/plugin/boot.ts
+++ b/packages/core/src/plugin/boot.ts
@@ -21,42 +21,47 @@ export interface Interface {
 
 export class Service extends Context.Service()("@opencode/v2/PluginBoot") {}
 
-export const layer: Layer.Layer = Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const catalog = yield* Catalog.Service
-    const plugin = yield* PluginV2.Service
-    const auth = yield* AuthV2.Service
-    const npm = yield* Npm.Service
-    const done = yield* Deferred.make()
+export const layer: Layer.Layer =
+  Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const catalog = yield* Catalog.Service
+      const plugin = yield* PluginV2.Service
+      const auth = yield* AuthV2.Service
+      const npm = yield* Npm.Service
+      const done = yield* Deferred.make()
 
-    const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) {
-      yield* plugin.add({
-        id: input.id,
-        effect: input.effect.pipe(
-          Effect.provideService(Catalog.Service, catalog),
-          Effect.provideService(AuthV2.Service, auth),
-          Effect.provideService(Npm.Service, npm),
-        ),
+      const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) {
+        yield* plugin.add({
+          id: input.id,
+          effect: input.effect.pipe(
+            Effect.provideService(Catalog.Service, catalog),
+            Effect.provideService(AuthV2.Service, auth),
+            Effect.provideService(Npm.Service, npm),
+          ),
+        })
       })
-    })
 
-    const boot = Effect.gen(function* () {
-      yield* add(EnvPlugin)
-      yield* add(AuthPlugin)
-      for (const item of ProviderPlugins) {
-        yield* add(item)
-      }
-      yield* add(ModelsDevPlugin)
-    }).pipe(Effect.withSpan("PluginBoot.boot"))
+      const boot = Effect.gen(function* () {
+        yield* add(EnvPlugin)
+        yield* add(AuthPlugin)
+        for (const item of ProviderPlugins) {
+          yield* add(item)
+        }
+        yield* add(ModelsDevPlugin)
+      }).pipe(Effect.withSpan("PluginBoot.boot"))
 
-    yield* boot.pipe(Effect.exit, Effect.flatMap((exit) => Deferred.done(done, exit)), Effect.forkScoped)
+      yield* boot.pipe(
+        Effect.exit,
+        Effect.flatMap((exit) => Deferred.done(done, exit)),
+        Effect.forkScoped,
+      )
 
-    return Service.of({
-      wait: () => Deferred.await(done),
-    })
-  }),
-)
+      return Service.of({
+        wait: () => Deferred.await(done),
+      })
+    }),
+  )
 
 export const defaultLayer = layer.pipe(
   Layer.provide(Catalog.defaultLayer),
diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts
index cd7aed48d..47b8ec896 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/model.ts
@@ -9,13 +9,15 @@ export const ModelGroup = HttpApiGroup.make("v2.model")
     HttpApiEndpoint.get("models", "/api/model", {
       query: InstanceQuery,
       success: Schema.Array(ModelV2.Info),
-    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
-      OpenApi.annotations({
-        identifier: "v2.model.list",
-        summary: "List v2 models",
-        description: "Retrieve available v2 models ordered by release date.",
-      }),
-    ),
+    })
+      .annotateMerge(instanceQueryOpenApi)
+      .annotateMerge(
+        OpenApi.annotations({
+          identifier: "v2.model.list",
+          summary: "List v2 models",
+          description: "Retrieve available v2 models ordered by release date.",
+        }),
+      ),
   )
   .annotateMerge(
     OpenApi.annotations({
diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts
index a44335d4c..e62cfc324 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/provider.ts
@@ -10,13 +10,15 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
     HttpApiEndpoint.get("providers", "/api/provider", {
       query: InstanceQuery,
       success: Schema.Array(ProviderV2.Info),
-    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
-      OpenApi.annotations({
-        identifier: "v2.provider.list",
-        summary: "List v2 providers",
-        description: "Retrieve active v2 AI providers so clients can show provider availability and configuration.",
-      }),
-    ),
+    })
+      .annotateMerge(instanceQueryOpenApi)
+      .annotateMerge(
+        OpenApi.annotations({
+          identifier: "v2.provider.list",
+          summary: "List v2 providers",
+          description: "Retrieve active v2 AI providers so clients can show provider availability and configuration.",
+        }),
+      ),
   )
   .add(
     HttpApiEndpoint.get("provider", "/api/provider/:providerID", {
@@ -24,13 +26,16 @@ export const ProviderGroup = HttpApiGroup.make("v2.provider")
       query: InstanceQuery,
       success: ProviderV2.Info,
       error: ApiNotFoundError,
-    }).annotateMerge(instanceQueryOpenApi).annotateMerge(
-      OpenApi.annotations({
-        identifier: "v2.provider.get",
-        summary: "Get v2 provider",
-        description: "Retrieve a single v2 AI provider so clients can inspect its availability and endpoint settings.",
-      }),
-    ),
+    })
+      .annotateMerge(instanceQueryOpenApi)
+      .annotateMerge(
+        OpenApi.annotations({
+          identifier: "v2.provider.get",
+          summary: "Get v2 provider",
+          description:
+            "Retrieve a single v2 AI provider so clients can inspect its availability and endpoint settings.",
+        }),
+      ),
   )
   .annotateMerge(
     OpenApi.annotations({
diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json
index 114db9cd7..d40d55cf6 100644
--- a/packages/sdk/openapi.json
+++ b/packages/sdk/openapi.json
@@ -7610,7 +7610,27 @@
       "get": {
         "tags": ["v2 models"],
         "operationId": "v2.model.list",
-        "parameters": [],
+        "parameters": [
+          {
+            "name": "instance",
+            "in": "query",
+            "schema": {
+              "type": "object",
+              "properties": {
+                "directory": {
+                  "type": "string"
+                },
+                "workspace": {
+                  "type": "string"
+                }
+              },
+              "additionalProperties": false
+            },
+            "required": false,
+            "style": "deepObject",
+            "explode": true
+          }
+        ],
         "responses": {
           "200": {
             "description": "Success",
@@ -7640,7 +7660,27 @@
       "get": {
         "tags": ["v2 providers"],
         "operationId": "v2.provider.list",
-        "parameters": [],
+        "parameters": [
+          {
+            "name": "instance",
+            "in": "query",
+            "schema": {
+              "type": "object",
+              "properties": {
+                "directory": {
+                  "type": "string"
+                },
+                "workspace": {
+                  "type": "string"
+                }
+              },
+              "additionalProperties": false
+            },
+            "required": false,
+            "style": "deepObject",
+            "explode": true
+          }
+        ],
         "responses": {
           "200": {
             "description": "Success",
@@ -7678,6 +7718,25 @@
               "type": "string"
             },
             "required": true
+          },
+          {
+            "name": "instance",
+            "in": "query",
+            "schema": {
+              "type": "object",
+              "properties": {
+                "directory": {
+                  "type": "string"
+                },
+                "workspace": {
+                  "type": "string"
+                }
+              },
+              "additionalProperties": false
+            },
+            "required": false,
+            "style": "deepObject",
+            "explode": true
           }
         ],
         "responses": {
@@ -9014,12 +9073,6 @@
           {
             "$ref": "#/components/schemas/EventSessionError"
           },
-          {
-            "$ref": "#/components/schemas/EventInstallationUpdated"
-          },
-          {
-            "$ref": "#/components/schemas/EventInstallationUpdate-available"
-          },
           {
             "$ref": "#/components/schemas/EventQuestionAsked"
           },
@@ -9095,6 +9148,12 @@
           {
             "$ref": "#/components/schemas/EventPtyDeleted"
           },
+          {
+            "$ref": "#/components/schemas/EventInstallationUpdated"
+          },
+          {
+            "$ref": "#/components/schemas/EventInstallationUpdate-available"
+          },
           {
             "$ref": "#/components/schemas/EventMessageUpdated"
           },
@@ -11313,12 +11372,6 @@
               {
                 "$ref": "#/components/schemas/EventSessionError"
               },
-              {
-                "$ref": "#/components/schemas/EventInstallationUpdated"
-              },
-              {
-                "$ref": "#/components/schemas/EventInstallationUpdate-available"
-              },
               {
                 "$ref": "#/components/schemas/EventQuestionAsked"
               },
@@ -11394,6 +11447,12 @@
               {
                 "$ref": "#/components/schemas/EventPtyDeleted"
               },
+              {
+                "$ref": "#/components/schemas/EventInstallationUpdated"
+              },
+              {
+                "$ref": "#/components/schemas/EventInstallationUpdate-available"
+              },
               {
                 "$ref": "#/components/schemas/EventMessageUpdated"
               },
@@ -16469,54 +16528,6 @@
         "required": ["id", "type", "properties"],
         "additionalProperties": false
       },
-      "EventInstallationUpdated": {
-        "type": "object",
-        "properties": {
-          "id": {
-            "type": "string"
-          },
-          "type": {
-            "type": "string",
-            "enum": ["installation.updated"]
-          },
-          "properties": {
-            "type": "object",
-            "properties": {
-              "version": {
-                "type": "string"
-              }
-            },
-            "required": ["version"],
-            "additionalProperties": false
-          }
-        },
-        "required": ["id", "type", "properties"],
-        "additionalProperties": false
-      },
-      "EventInstallationUpdate-available": {
-        "type": "object",
-        "properties": {
-          "id": {
-            "type": "string"
-          },
-          "type": {
-            "type": "string",
-            "enum": ["installation.update-available"]
-          },
-          "properties": {
-            "type": "object",
-            "properties": {
-              "version": {
-                "type": "string"
-              }
-            },
-            "required": ["version"],
-            "additionalProperties": false
-          }
-        },
-        "required": ["id", "type", "properties"],
-        "additionalProperties": false
-      },
       "EventQuestionAsked": {
         "type": "object",
         "properties": {
@@ -17033,6 +17044,54 @@
         "required": ["id", "type", "properties"],
         "additionalProperties": false
       },
+      "EventInstallationUpdated": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "string"
+          },
+          "type": {
+            "type": "string",
+            "enum": ["installation.updated"]
+          },
+          "properties": {
+            "type": "object",
+            "properties": {
+              "version": {
+                "type": "string"
+              }
+            },
+            "required": ["version"],
+            "additionalProperties": false
+          }
+        },
+        "required": ["id", "type", "properties"],
+        "additionalProperties": false
+      },
+      "EventInstallationUpdate-available": {
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "string"
+          },
+          "type": {
+            "type": "string",
+            "enum": ["installation.update-available"]
+          },
+          "properties": {
+            "type": "object",
+            "properties": {
+              "version": {
+                "type": "string"
+              }
+            },
+            "required": ["version"],
+            "additionalProperties": false
+          }
+        },
+        "required": ["id", "type", "properties"],
+        "additionalProperties": false
+      },
       "EventMessageUpdated": {
         "type": "object",
         "properties": {

From 5c35ea2181c3b61f68325c1c017848681a863b6f Mon Sep 17 00:00:00 2001
From: Sebastian 
Date: Thu, 14 May 2026 03:01:25 +0200
Subject: [PATCH 313/378] notification docs (#27406)

---
 packages/web/src/content/docs/config.mdx | 10 +++++++++-
 packages/web/src/content/docs/tui.mdx    | 24 +++++++++++++++++++++++-
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx
index ec96069c7..1af77a06e 100644
--- a/packages/web/src/content/docs/config.mdx
+++ b/packages/web/src/content/docs/config.mdx
@@ -273,12 +273,20 @@ Use a dedicated `tui.json` (or `tui.jsonc`) file for TUI-specific settings.
     "enabled": true
   },
   "diff_style": "auto",
-  "mouse": true
+  "mouse": true,
+  "attention": {
+    "enabled": true,
+    "notifications": true,
+    "sound": true,
+    "volume": 0.4
+  }
 }
 ```
 
 Use `OPENCODE_TUI_CONFIG` to point to a custom TUI config file.
 
+Set `attention.enabled` to turn on TUI desktop notifications and sounds. See [TUI attention](/docs/tui#attention).
+
 Legacy `theme`, `keybinds`, and `tui` keys in `opencode.json` are deprecated and automatically migrated when possible.
 
 ---
diff --git a/packages/web/src/content/docs/tui.mdx b/packages/web/src/content/docs/tui.mdx
index b9103c702..cd668a3be 100644
--- a/packages/web/src/content/docs/tui.mdx
+++ b/packages/web/src/content/docs/tui.mdx
@@ -369,7 +369,17 @@ You can customize TUI behavior through `tui.json` (or `tui.jsonc`).
     "enabled": false
   },
   "diff_style": "auto",
-  "mouse": true
+  "mouse": true,
+  "attention": {
+    "enabled": true,
+    "notifications": true,
+    "sound": true,
+    "volume": 0.4,
+    "sound_pack": "opencode.default",
+    "sounds": {
+      "error": "./sounds/error.mp3"
+    }
+  }
 }
 ```
 
@@ -386,9 +396,21 @@ This is separate from `opencode.json`, which configures server/runtime behavior.
 - `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.
+- `attention` - Configures TUI desktop notifications and sounds. Disabled by default.
 
 Use `OPENCODE_TUI_CONFIG` to load a custom TUI config path.
 
+### Attention
+
+The TUI can request attention for questions, permissions, session errors, and completed sessions. Enable it with `attention.enabled`; built-in events play sounds when triggered, and non-subagent events request desktop notifications only when the terminal is blurred.
+
+- `enabled` - Enable all attention notifications and sounds. Defaults to `false`.
+- `notifications` - Allow terminal-mediated desktop notifications when attention is enabled. Defaults to `true`.
+- `sound` - Allow attention sounds when attention is enabled. Defaults to `true`.
+- `volume` - Default sound volume from `0` to `1`. Defaults to `0.4`.
+- `sound_pack` - Sound pack ID to use. Defaults to `opencode.default`.
+- `sounds` - Override sound files for `default`, `question`, `permission`, `error`, `done`, or `subagent_done`. Paths can be absolute, `file://` URLs, or relative to `tui.json`.
+
 ---
 
 ## Customization

From 8e353584c7fdf14e65cdc6f547a9570a801ae956 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 21:02:34 -0400
Subject: [PATCH 314/378] test(format): remove formatter check sleeps (#27407)

---
 packages/opencode/test/format/format.test.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/opencode/test/format/format.test.ts b/packages/opencode/test/format/format.test.ts
index 674d2767c..c9e57d204 100644
--- a/packages/opencode/test/format/format.test.ts
+++ b/packages/opencode/test/format/format.test.ts
@@ -186,14 +186,14 @@ describe("Format", () => {
               Formatter.gofmt.enabled = async () => {
                 active++
                 max = Math.max(max, active)
-                await Bun.sleep(20)
+                await Promise.resolve()
                 active--
                 return ["sh", "-c", "true"]
               }
               Formatter.mix.enabled = async () => {
                 active++
                 max = Math.max(max, active)
-                await Bun.sleep(20)
+                await Promise.resolve()
                 active--
                 return ["sh", "-c", "true"]
               }

From 3fc7486d154693e2150803ebec0b09d3b60454ed Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 21:10:40 -0400
Subject: [PATCH 315/378] test(session): fix shell-cancel race when trap hasn't
 installed yet (#27408)

---
 packages/opencode/test/session/prompt.test.ts | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts
index c3d113d52..c87e11880 100644
--- a/packages/opencode/test/session/prompt.test.ts
+++ b/packages/opencode/test/session/prompt.test.ts
@@ -1512,11 +1512,26 @@ unix(
     withSh(() =>
       Effect.gen(function* () {
         const { prompt, chat } = yield* boot()
+        const { directory: dir } = yield* TestInstance
+        const afs = yield* AppFileSystem.Service
+        const ready = path.join(dir, ".trap-ready")
 
         const sh = yield* prompt
-          .shell({ sessionID: chat.id, agent: "build", command: "trap '' TERM; sleep 30" })
+          .shell({
+            sessionID: chat.id,
+            agent: "build",
+            // Touch marker AFTER trap installs so the test waits for the actual
+            // ignore-TERM state before cancelling; otherwise SIGTERM can arrive
+            // before `trap` runs and the escalation path is never exercised.
+            command: `trap '' TERM; touch "${ready}"; sleep 30`,
+          })
           .pipe(Effect.forkChild)
-        yield* Effect.sleep(50)
+
+        yield* Effect.gen(function* () {
+          while (!(yield* afs.existsSafe(ready))) {
+            yield* Effect.sleep(Duration.millis(10))
+          }
+        }).pipe(Effect.timeout(Duration.seconds(5)))
 
         yield* prompt.cancel(chat.id)
 

From edf76494008e3f01342b095e558a34b0c2e24706 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 21:28:04 -0400
Subject: [PATCH 316/378] fix(session): type busy errors (#27410)

---
 packages/opencode/src/effect/runner.ts        | 19 ++++++--------
 .../instance/httpapi/handlers/session.ts      | 11 +++-----
 packages/opencode/src/session/prompt.ts       | 14 +++++------
 packages/opencode/src/session/revert.ts       |  4 +--
 packages/opencode/src/session/run-state.ts    | 17 +++++++------
 packages/opencode/src/session/session.ts      |  8 +++---
 packages/opencode/test/effect/runner.test.ts  | 25 ++-----------------
 packages/opencode/test/session/prompt.test.ts |  4 ++-
 8 files changed, 38 insertions(+), 64 deletions(-)

diff --git a/packages/opencode/src/effect/runner.ts b/packages/opencode/src/effect/runner.ts
index 5d7e8778d..f21a61c97 100644
--- a/packages/opencode/src/effect/runner.ts
+++ b/packages/opencode/src/effect/runner.ts
@@ -4,11 +4,12 @@ export interface Runner {
   readonly state: State
   readonly busy: boolean
   readonly ensureRunning: (work: Effect.Effect) => Effect.Effect
-  readonly startShell: (work: Effect.Effect, ready?: Latch.Latch) => Effect.Effect
+  readonly startShell: (work: Effect.Effect, ready?: Latch.Latch) => Effect.Effect
   readonly cancel: Effect.Effect
 }
 
 export class Cancelled extends Schema.TaggedErrorClass()("RunnerCancelled", {}) {}
+export class Busy extends Schema.TaggedErrorClass()("RunnerBusy", {}) {}
 
 interface RunHandle {
   id: number
@@ -41,12 +42,11 @@ export const make = (
     onIdle?: Effect.Effect
     onBusy?: Effect.Effect
     onInterrupt?: Effect.Effect
-    busy?: () => never
   },
 ): Runner => {
   const ref = SynchronizedRef.makeUnsafe>({ _tag: "Idle" })
   const idle = opts?.onIdle ?? Effect.void
-  const busy = opts?.onBusy ?? Effect.void
+  const onBusy = opts?.onBusy ?? Effect.void
   const onInterrupt = opts?.onInterrupt
   let ids = 0
 
@@ -137,20 +137,15 @@ export const make = (
       }),
     ).pipe(Effect.flatten)
 
-  const startShell = (work: Effect.Effect, ready?: Latch.Latch) =>
+  const startShell = (work: Effect.Effect, ready?: Latch.Latch): Effect.Effect =>
     SynchronizedRef.modifyEffect(
       ref,
       Effect.fnUntraced(function* (st) {
         if (st._tag !== "Idle") {
-          return [
-            Effect.sync(() => {
-              if (opts?.busy) opts.busy()
-              throw new Error("Runner is busy")
-            }),
-            st,
-          ] as const
+          const reject: Effect.Effect = Effect.fail(new Busy())
+          return [reject, st] as const
         }
-        yield* busy
+        yield* onBusy
         const id = next()
         const cancelled = yield* Deferred.make()
         const fiber = yield* work.pipe(Effect.ensuring(finishShell(id)), Effect.forkChild)
diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
index 1fb2455ab..e3f79965d 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
@@ -58,13 +58,10 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
     const bus = yield* Bus.Service
     const scope = yield* Scope.Scope
 
-    const mapBusy = (effect: Effect.Effect): Effect.Effect =>
-      effect.pipe(
-        Effect.catchCause((cause): Effect.Effect => {
-          if (Cause.squash(cause) instanceof Session.BusyError) return Effect.fail(new HttpApiError.BadRequest({}))
-          return Effect.failCause(cause)
-        }),
-      )
+    const mapBusy = (
+      effect: Effect.Effect,
+    ): Effect.Effect =>
+      effect.pipe(Effect.catchTag("SessionBusyError", () => Effect.fail(new HttpApiError.BadRequest({}))))
 
     const list = Effect.fn("SessionHttpApi.list")(function* (ctx: { query: typeof ListQuery.Type }) {
       return yield* session.list({
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 1ae411426..12a9e1a8b 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -167,7 +167,7 @@ export interface Interface {
   readonly cancel: (sessionID: SessionID) => Effect.Effect
   readonly prompt: (input: PromptInput) => Effect.Effect
   readonly loop: (input: LoopInput) => Effect.Effect
-  readonly shell: (input: ShellInput) => Effect.Effect
+  readonly shell: (input: ShellInput) => Effect.Effect
   readonly command: (input: CommandInput) => Effect.Effect
   readonly resolvePromptParts: (template: string) => Effect.Effect
 }
@@ -1864,12 +1864,12 @@ NOTE: At any point in time through this workflow you should feel free to ask the
       return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID))
     })
 
-    const shell: (input: ShellInput) => Effect.Effect = Effect.fn("SessionPrompt.shell")(
-      function* (input: ShellInput) {
-        const ready = yield* Latch.make()
-        return yield* state.startShell(input.sessionID, lastAssistant(input.sessionID), shellImpl(input, ready), ready)
-      },
-    )
+    const shell: (input: ShellInput) => Effect.Effect = Effect.fn(
+      "SessionPrompt.shell",
+    )(function* (input: ShellInput) {
+      const ready = yield* Latch.make()
+      return yield* state.startShell(input.sessionID, lastAssistant(input.sessionID), shellImpl(input, ready), ready)
+    })
 
     const command = Effect.fn("SessionPrompt.command")(function* (input: CommandInput) {
       yield* elog.info("command", { sessionID: input.sessionID, command: input.command, agent: input.agent })
diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts
index 8ba7b265b..950d533a3 100644
--- a/packages/opencode/src/session/revert.ts
+++ b/packages/opencode/src/session/revert.ts
@@ -20,8 +20,8 @@ export const RevertInput = Schema.Struct({
 export type RevertInput = Schema.Schema.Type
 
 export interface Interface {
-  readonly revert: (input: RevertInput) => Effect.Effect
-  readonly unrevert: (input: { sessionID: SessionID }) => Effect.Effect
+  readonly revert: (input: RevertInput) => Effect.Effect
+  readonly unrevert: (input: { sessionID: SessionID }) => Effect.Effect
   readonly cleanup: (session: Session.Info) => Effect.Effect
 }
 
diff --git a/packages/opencode/src/session/run-state.ts b/packages/opencode/src/session/run-state.ts
index 9d4986f17..e33dba005 100644
--- a/packages/opencode/src/session/run-state.ts
+++ b/packages/opencode/src/session/run-state.ts
@@ -7,7 +7,7 @@ import { SessionID } from "./schema"
 import { SessionStatus } from "./status"
 
 export interface Interface {
-  readonly assertNotBusy: (sessionID: SessionID) => Effect.Effect
+  readonly assertNotBusy: (sessionID: SessionID) => Effect.Effect
   readonly cancel: (sessionID: SessionID) => Effect.Effect
   readonly ensureRunning: (
     sessionID: SessionID,
@@ -19,7 +19,7 @@ export interface Interface {
     onInterrupt: Effect.Effect,
     work: Effect.Effect,
     ready?: Latch.Latch,
-  ) => Effect.Effect
+  ) => Effect.Effect
 }
 
 export class Service extends Context.Service()("@opencode/SessionRunState") {}
@@ -60,9 +60,6 @@ export const layer = Layer.effect(
         }),
         onBusy: status.set(sessionID, { type: "busy" }),
         onInterrupt,
-        busy: () => {
-          throw new Session.BusyError(sessionID)
-        },
       })
       data.runners.set(sessionID, next)
       return next
@@ -71,7 +68,7 @@ export const layer = Layer.effect(
     const assertNotBusy = Effect.fn("SessionRunState.assertNotBusy")(function* (sessionID: SessionID) {
       const data = yield* InstanceState.get(state)
       const existing = data.runners.get(sessionID)
-      if (existing?.busy) throw new Session.BusyError(sessionID)
+      if (existing?.busy) yield* busyError(sessionID)
     })
 
     const cancel = Effect.fn("SessionRunState.cancel")(function* (sessionID: SessionID) {
@@ -98,7 +95,9 @@ export const layer = Layer.effect(
       work: Effect.Effect,
       ready?: Latch.Latch,
     ) {
-      return yield* (yield* runner(sessionID, onInterrupt)).startShell(work, ready)
+      return yield* (yield* runner(sessionID, onInterrupt))
+        .startShell(work, ready)
+        .pipe(Effect.catchTag("RunnerBusy", () => Effect.fail(busyError(sessionID))))
     })
 
     return Service.of({ assertNotBusy, cancel, ensureRunning, startShell })
@@ -107,4 +106,8 @@ export const layer = Layer.effect(
 
 export const defaultLayer = layer.pipe(Layer.provide(SessionStatus.defaultLayer))
 
+function busyError(sessionID: SessionID) {
+  return new Session.BusyError({ sessionID })
+}
+
 export * as SessionRunState from "./run-state"
diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts
index 85486480a..3fb924dc8 100644
--- a/packages/opencode/src/session/session.ts
+++ b/packages/opencode/src/session/session.ts
@@ -442,11 +442,9 @@ export const getUsage = (input: { model: Provider.Model; usage: LanguageModelUsa
   }
 }
 
-export class BusyError extends Error {
-  constructor(public readonly sessionID: string) {
-    super(`Session ${sessionID} is busy`)
-  }
-}
+export class BusyError extends Schema.TaggedErrorClass()("SessionBusyError", {
+  sessionID: SessionID,
+}) {}
 
 export type NotFound = NotFoundError
 
diff --git a/packages/opencode/test/effect/runner.test.ts b/packages/opencode/test/effect/runner.test.ts
index 3030ca64e..27fe9e025 100644
--- a/packages/opencode/test/effect/runner.test.ts
+++ b/packages/opencode/test/effect/runner.test.ts
@@ -1,5 +1,5 @@
 import { describe, expect } from "bun:test"
-import { Deferred, Effect, Exit, Fiber, Latch, Ref, Scope } from "effect"
+import { Cause, Deferred, Effect, Exit, Fiber, Latch, Ref, Scope } from "effect"
 import { Runner } from "@/effect/runner"
 import { it } from "../lib/effect"
 
@@ -302,34 +302,13 @@ describe("Runner", () => {
 
       const exit = yield* runner.startShell(Effect.succeed("second")).pipe(Effect.exit)
       expect(Exit.isFailure(exit)).toBe(true)
+      if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Runner.Busy)
 
       yield* Deferred.succeed(gate, undefined)
       yield* Fiber.await(sh)
     }),
   )
 
-  it.live(
-    "shell rejects via busy callback and cancel still stops the first shell",
-    Effect.gen(function* () {
-      const s = yield* Scope.Scope
-      const runner = Runner.make(s, {
-        busy: () => {
-          throw new Error("busy")
-        },
-      })
-
-      const sh = yield* runner.startShell(Effect.never.pipe(Effect.as("aborted"))).pipe(Effect.forkChild)
-      yield* waitForState(runner, "Shell")
-
-      const exit = yield* runner.startShell(Effect.succeed("second")).pipe(Effect.exit)
-      expect(Exit.isFailure(exit)).toBe(true)
-
-      yield* runner.cancel
-      const done = yield* Fiber.await(sh)
-      expect(Exit.isFailure(done)).toBe(true)
-    }),
-  )
-
   it.live(
     "cancel interrupts shell",
     Effect.gen(function* () {
diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts
index c87e11880..9ad2fbe1f 100644
--- a/packages/opencode/test/session/prompt.test.ts
+++ b/packages/opencode/test/session/prompt.test.ts
@@ -1119,7 +1119,7 @@ it.instance(
 )
 
 it.instance(
-  "assertNotBusy throws BusyError when loop running",
+  "assertNotBusy fails with BusyError when loop running",
   () =>
     Effect.gen(function* () {
       const { llm } = yield* useServerConfig(providerCfg)
@@ -1138,6 +1138,7 @@ it.instance(
       expect(Exit.isFailure(exit)).toBe(true)
       if (Exit.isFailure(exit)) {
         expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError)
+        expect(Cause.squash(exit.cause)).toMatchObject({ _tag: "SessionBusyError", sessionID: chat.id })
       }
 
       yield* prompt.cancel(chat.id)
@@ -1181,6 +1182,7 @@ it.instance(
       expect(Exit.isFailure(exit)).toBe(true)
       if (Exit.isFailure(exit)) {
         expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError)
+        expect(Cause.squash(exit.cause)).toMatchObject({ _tag: "SessionBusyError", sessionID: chat.id })
       }
 
       yield* prompt.cancel(chat.id)

From 681594b5516215980b7c1e293b19b62d7e07c043 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 21:39:50 -0400
Subject: [PATCH 317/378] refactor(storage): remove not found wire serializer
 (#27416)

---
 packages/opencode/src/storage/storage.ts | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts
index ee2ee45f8..706c24eae 100644
--- a/packages/opencode/src/storage/storage.ts
+++ b/packages/opencode/src/storage/storage.ts
@@ -20,13 +20,6 @@ export class NotFoundError extends Schema.TaggedErrorClass()("Not
   static isInstance(input: unknown): input is NotFoundError {
     return input instanceof NotFoundError
   }
-
-  toObject() {
-    return {
-      name: "NotFoundError" as const,
-      data: { message: this.message },
-    }
-  }
 }
 
 export type Error = AppFileSystem.Error | NotFoundError

From b0ade402658db15b70172343cda0eca9c47c4b85 Mon Sep 17 00:00:00 2001
From: Sebastian 
Date: Thu, 14 May 2026 03:53:48 +0200
Subject: [PATCH 318/378] flip back to markdown renderable (#27421)

---
 packages/core/src/flag/flag.ts                         |  6 ------
 .../opencode/src/cli/cmd/tui/routes/session/index.tsx  | 10 +++++-----
 packages/web/src/content/docs/ar/cli.mdx               |  1 -
 packages/web/src/content/docs/bs/cli.mdx               |  1 -
 packages/web/src/content/docs/cli.mdx                  |  1 -
 packages/web/src/content/docs/da/cli.mdx               |  1 -
 packages/web/src/content/docs/de/cli.mdx               |  1 -
 packages/web/src/content/docs/es/cli.mdx               |  1 -
 packages/web/src/content/docs/fr/cli.mdx               |  1 -
 packages/web/src/content/docs/it/cli.mdx               |  1 -
 packages/web/src/content/docs/ja/cli.mdx               |  1 -
 packages/web/src/content/docs/ko/cli.mdx               |  1 -
 packages/web/src/content/docs/nb/cli.mdx               |  1 -
 packages/web/src/content/docs/pl/cli.mdx               |  1 -
 packages/web/src/content/docs/pt-br/cli.mdx            |  1 -
 packages/web/src/content/docs/ru/cli.mdx               |  1 -
 packages/web/src/content/docs/th/cli.mdx               |  1 -
 packages/web/src/content/docs/tr/cli.mdx               |  1 -
 packages/web/src/content/docs/zh-cn/cli.mdx            |  2 +-
 packages/web/src/content/docs/zh-tw/cli.mdx            |  1 -
 20 files changed, 6 insertions(+), 29 deletions(-)

diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts
index f76d1aaf9..ec417cf5b 100644
--- a/packages/core/src/flag/flag.ts
+++ b/packages/core/src/flag/flag.ts
@@ -5,11 +5,6 @@ function truthy(key: string) {
   return value === "true" || value === "1"
 }
 
-function falsy(key: string) {
-  const value = process.env[key]?.toLowerCase()
-  return value === "false" || value === "0"
-}
-
 function number(key: string) {
   const value = process.env[key]
   if (!value) return undefined
@@ -72,7 +67,6 @@ export const Flag = {
   OPENCODE_EXPERIMENTAL_LSP_TOOL: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
   OPENCODE_EXPERIMENTAL_PLAN_MODE: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
   OPENCODE_EXPERIMENTAL_SCOUT: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SCOUT"),
-  OPENCODE_EXPERIMENTAL_MARKDOWN: !falsy("OPENCODE_EXPERIMENTAL_MARKDOWN"),
   OPENCODE_ENABLE_PARALLEL: truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL"),
   OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
   OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
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 b5e8e1028..76cb35a16 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -1528,14 +1528,14 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess
   return (
     
       
-        
       
     
diff --git a/packages/web/src/content/docs/ar/cli.mdx b/packages/web/src/content/docs/ar/cli.mdx
index 8a9729436..6fd419b69 100644
--- a/packages/web/src/content/docs/ar/cli.mdx
+++ b/packages/web/src/content/docs/ar/cli.mdx
@@ -607,5 +607,4 @@ opencode upgrade v0.1.48
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | تعطيل مراقب الملفات                         |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | تفعيل ميزات Exa التجريبية                   |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | تمكين TY LSP لملفات python                  |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | تفعيل ميزات markdown تجريبية                |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | تفعيل وضع الخطة                             |
diff --git a/packages/web/src/content/docs/bs/cli.mdx b/packages/web/src/content/docs/bs/cli.mdx
index c7944e7cf..b40e9865f 100644
--- a/packages/web/src/content/docs/bs/cli.mdx
+++ b/packages/web/src/content/docs/bs/cli.mdx
@@ -605,5 +605,4 @@ Ove varijable okruženja omogućavaju eksperimentalne karakteristike koje se mog
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Onemogući praćenje datoteka                       |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Omogući eksperimentalne Exa funkcije              |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Omogući TY LSP za python datoteke                 |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | Omogući eksperimentalne Markdown funkcije         |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Omogući Plan mod                                  |
diff --git a/packages/web/src/content/docs/cli.mdx b/packages/web/src/content/docs/cli.mdx
index ac8a1a304..980ac7d05 100644
--- a/packages/web/src/content/docs/cli.mdx
+++ b/packages/web/src/content/docs/cli.mdx
@@ -723,5 +723,4 @@ These environment variables enable experimental features that may change or be r
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Disable file watcher                    |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Enable experimental Exa features        |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Enable TY LSP for python files          |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | Enable experimental markdown features   |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Enable plan mode                        |
diff --git a/packages/web/src/content/docs/da/cli.mdx b/packages/web/src/content/docs/da/cli.mdx
index 02b1b4987..81c8df898 100644
--- a/packages/web/src/content/docs/da/cli.mdx
+++ b/packages/web/src/content/docs/da/cli.mdx
@@ -608,5 +608,4 @@ Disse miljøvariabler muliggør eksperimentelle funktioner, der kan ændres elle
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Deaktiver filovervågning                   |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Aktive eksperimenter Exa-funktioner        |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Aktiver TY LSP for python-filer            |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | Aktive eksperimentelle markdown-funktioner |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Aktiver plantilstand                       |
diff --git a/packages/web/src/content/docs/de/cli.mdx b/packages/web/src/content/docs/de/cli.mdx
index 94e9c88fa..1151e5146 100644
--- a/packages/web/src/content/docs/de/cli.mdx
+++ b/packages/web/src/content/docs/de/cli.mdx
@@ -607,5 +607,4 @@ Diese Umgebungsvariablen ermöglichen experimentelle Funktionen, die sich änder
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolescher Wert | Dateiüberwachung deaktivieren                           |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolescher Wert | Experimentelle Exa-Funktionen aktivieren                |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolescher Wert | TY LSP für Python-Dateien aktivieren                    |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolescher Wert | Experimentelle Markdown-Funktionen aktivieren           |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolescher Wert | Planmodus aktivieren                                    |
diff --git a/packages/web/src/content/docs/es/cli.mdx b/packages/web/src/content/docs/es/cli.mdx
index 6b66e7b5f..cff38c175 100644
--- a/packages/web/src/content/docs/es/cli.mdx
+++ b/packages/web/src/content/docs/es/cli.mdx
@@ -607,5 +607,4 @@ Estas variables de entorno habilitan funciones experimentales que pueden cambiar
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | booleano | Deshabilitar el observador de archivos                     |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | booleano | Habilitar funciones experimentales de Exa                  |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | booleano | Habilitar Habilitar TY LSP para archivos python            |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | booleano | Habilitar funciones de Markdown experimentales             |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | booleano | Habilitar modo de plan                                     |
diff --git a/packages/web/src/content/docs/fr/cli.mdx b/packages/web/src/content/docs/fr/cli.mdx
index c5455654a..d5fb6112e 100644
--- a/packages/web/src/content/docs/fr/cli.mdx
+++ b/packages/web/src/content/docs/fr/cli.mdx
@@ -608,5 +608,4 @@ Ces variables d'environnement activent des fonctionnalités expérimentales qui
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | booléen | Désactiver l'observateur de fichiers                            |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | booléen | Activer les fonctionnalités Exa expérimentales                  |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | booléen | Activer TY LSP pour les fichiers python                         |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | booléen | Activer les fonctionnalités Markdown expérimentales             |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | booléen | Activer le mode plan                                            |
diff --git a/packages/web/src/content/docs/it/cli.mdx b/packages/web/src/content/docs/it/cli.mdx
index 6fd3d5676..a07a41925 100644
--- a/packages/web/src/content/docs/it/cli.mdx
+++ b/packages/web/src/content/docs/it/cli.mdx
@@ -608,5 +608,4 @@ Queste variabili d'ambiente abilitano funzionalità sperimentali che potrebbero
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Disabilita file watcher                    |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Abilita funzionalità Exa sperimentali      |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Abilita TY LSP per i file python           |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | Abilita markdown sperimentale              |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Abilita plan mode                          |
diff --git a/packages/web/src/content/docs/ja/cli.mdx b/packages/web/src/content/docs/ja/cli.mdx
index 120803627..715aebb02 100644
--- a/packages/web/src/content/docs/ja/cli.mdx
+++ b/packages/web/src/content/docs/ja/cli.mdx
@@ -607,5 +607,4 @@ OpenCode は環境変数を使用して構成できます。
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | ブール値 | ファイルウォッチャーを無効にする                 |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | ブール値 | 実験的な Exa 機能を有効にする                    |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | ブール値 | python ファイルの TY LSP を有効にする            |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | ブール値 | 試験的な Markdown 機能を有効にする               |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | ブール値 | プランモードを有効にする                         |
diff --git a/packages/web/src/content/docs/ko/cli.mdx b/packages/web/src/content/docs/ko/cli.mdx
index 7b829c7f3..0ee6aa576 100644
--- a/packages/web/src/content/docs/ko/cli.mdx
+++ b/packages/web/src/content/docs/ko/cli.mdx
@@ -607,5 +607,4 @@ OpenCode는 환경 변수로도 구성할 수 있습니다.
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | 파일 감시 비활성화               |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | 실험적 Exa 기능 활성화           |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | python 파일에 대해 TY LSP 활성화 |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | 실험적 Markdown 기능 활성화      |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Plan mode 활성화                 |
diff --git a/packages/web/src/content/docs/nb/cli.mdx b/packages/web/src/content/docs/nb/cli.mdx
index 824a3dcad..982f53906 100644
--- a/packages/web/src/content/docs/nb/cli.mdx
+++ b/packages/web/src/content/docs/nb/cli.mdx
@@ -608,5 +608,4 @@ Disse miljøvariablene muliggjør eksperimentelle funksjoner som kan endres elle
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolsk | Deaktiver filovervåking                       |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolsk | Aktiver eksperimentelle Exa-funksjoner        |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolsk | Aktiver TY LSP for python-filer               |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolsk | Aktiver eksperimentelle Markdown-funksjoner   |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolsk | Aktiver planmodus                             |
diff --git a/packages/web/src/content/docs/pl/cli.mdx b/packages/web/src/content/docs/pl/cli.mdx
index f2e4ddf9b..90e861168 100644
--- a/packages/web/src/content/docs/pl/cli.mdx
+++ b/packages/web/src/content/docs/pl/cli.mdx
@@ -608,5 +608,4 @@ Te zmienne włączają funkcje eksperymentalne, które mogą ulec zmianie lub zo
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Wyłącz obserwatora plików                    |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Włącz funkcje eksperymentalne Exa            |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Włącz TY LSP dla plików python               |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | Włącz funkcje eksperymentalne Markdown       |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Włącz tryb planowania                        |
diff --git a/packages/web/src/content/docs/pt-br/cli.mdx b/packages/web/src/content/docs/pt-br/cli.mdx
index 889626d41..a238aec9e 100644
--- a/packages/web/src/content/docs/pt-br/cli.mdx
+++ b/packages/web/src/content/docs/pt-br/cli.mdx
@@ -607,5 +607,4 @@ Essas variáveis de ambiente habilitam recursos experimentais que podem mudar ou
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Desabilitar monitoramento de arquivos                     |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Habilitar recursos experimentais do Exa                   |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Habilitar TY LSP para arquivos python                     |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | Habilitar recursos experimentais de markdown              |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Habilitar modo de plano                                   |
diff --git a/packages/web/src/content/docs/ru/cli.mdx b/packages/web/src/content/docs/ru/cli.mdx
index 5f52f3d7f..be6c2cd02 100644
--- a/packages/web/src/content/docs/ru/cli.mdx
+++ b/packages/web/src/content/docs/ru/cli.mdx
@@ -608,5 +608,4 @@ opencode можно настроить с помощью переменных с
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | логическое значение | Отключить просмотрщик файлов                           |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | логическое значение | Включить экспериментальные функции Exa                 |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | логическое значение | Включить TY LSP для файлов python                      |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | логическое значение | Включить экспериментальные функции Markdown            |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | логическое значение | Включить режим плана                                   |
diff --git a/packages/web/src/content/docs/th/cli.mdx b/packages/web/src/content/docs/th/cli.mdx
index d98722846..2c63bf6e8 100644
--- a/packages/web/src/content/docs/th/cli.mdx
+++ b/packages/web/src/content/docs/th/cli.mdx
@@ -609,5 +609,4 @@ OpenCode สามารถกำหนดค่าโดยใช้ตัว
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | Boolean | ปิดใช้งานตัวเฝ้าดูไฟล์                         |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | Boolean | ฟีเจอร์ Exa ทดลอง                              |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | Boolean | เปิดใช้งาน TY LSP สำหรับไฟล์ python            |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | Boolean | ใช้ Markdown renderer แบบทดลอง                 |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | Boolean | เปิดใช้งาน Plan mode                           |
diff --git a/packages/web/src/content/docs/tr/cli.mdx b/packages/web/src/content/docs/tr/cli.mdx
index 25b74ecfe..571d4058f 100644
--- a/packages/web/src/content/docs/tr/cli.mdx
+++ b/packages/web/src/content/docs/tr/cli.mdx
@@ -608,5 +608,4 @@ Bu ortam değişkenleri değişebilecek veya kaldırılabilecek deneysel özelli
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Dosya izleyiciyi devre dışı bırak                       |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Deneysel Exa özelliklerini etkinleştirin                |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | python dosyaları için TY LSP'yi etkinleştir             |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | Deneysel işaretleme özelliklerini etkinleştir           |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Plan modunu etkinleştir                                 |
diff --git a/packages/web/src/content/docs/zh-cn/cli.mdx b/packages/web/src/content/docs/zh-cn/cli.mdx
index aa992b673..7ae6a521a 100644
--- a/packages/web/src/content/docs/zh-cn/cli.mdx
+++ b/packages/web/src/content/docs/zh-cn/cli.mdx
@@ -607,5 +607,5 @@ OpenCode 可以通过环境变量进行配置。
 | `OPENCODE_EXPERIMENTAL_LSP_TOOL`                | boolean | 启用实验性 LSP 工具             |
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | 禁用文件监听器                  |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | 启用实验性 Exa 功能             |
-| `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | 为 python 文件启用 TY LSP       |     | `OPENCODE_EXPERIMENTAL_MARKDOWN` | boolean | 启用实验性 Markdown 功能 |
+| `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | 为 python 文件启用 TY LSP       |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | 启用计划模式                    |
diff --git a/packages/web/src/content/docs/zh-tw/cli.mdx b/packages/web/src/content/docs/zh-tw/cli.mdx
index 0d51bff2f..cf0189845 100644
--- a/packages/web/src/content/docs/zh-tw/cli.mdx
+++ b/packages/web/src/content/docs/zh-tw/cli.mdx
@@ -608,5 +608,4 @@ OpenCode 可以透過環境變數進行設定。
 | `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | 停用檔案監看器                  |
 | `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | 啟用實驗性 Exa 功能             |
 | `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | 為 python 檔案啟用 TY LSP       |
-| `OPENCODE_EXPERIMENTAL_MARKDOWN`                | boolean | 啟用實驗性 Markdown 功能        |
 | `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | 啟用計畫模式                    |

From 33bb33ba909b2bdb05ab306217faec417e56b06d Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]" 
Date: Thu, 14 May 2026 01:55:00 +0000
Subject: [PATCH 319/378] chore: generate

---
 packages/web/src/content/docs/da/cli.mdx    | 28 ++++++++++-----------
 packages/web/src/content/docs/zh-cn/cli.mdx |  2 +-
 2 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/packages/web/src/content/docs/da/cli.mdx b/packages/web/src/content/docs/da/cli.mdx
index 81c8df898..d903be55b 100644
--- a/packages/web/src/content/docs/da/cli.mdx
+++ b/packages/web/src/content/docs/da/cli.mdx
@@ -595,17 +595,17 @@ OpenCode kan konfigureres ved hjælp af miljøvariabler.
 
 Disse miljøvariabler muliggør eksperimentelle funktioner, der kan ændres eller fjernes.
 
-| Variabel                                        | Skriv   | Beskrivelse                                |
-| ----------------------------------------------- | ------- | ------------------------------------------ |
-| `OPENCODE_EXPERIMENTAL`                         | boolean | Aktiver alle eksperimentelle funktioner    |
-| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY`          | boolean | Aktiver ikonopdagelse                      |
-| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT`  | boolean | Deaktiver kopi ved valg i TUI              |
-| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | nummer  | Standard timeout for bash-kommandoer i ms  |
-| `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX`        | nummer  | Maks. output-tokens for LLM-svar           |
-| `OPENCODE_EXPERIMENTAL_FILEWATCHER`             | boolean | Aktiver filovervågning for hele dir        |
-| `OPENCODE_EXPERIMENTAL_OXFMT`                   | boolean | Aktiver oxfmt formatter                    |
-| `OPENCODE_EXPERIMENTAL_LSP_TOOL`                | boolean | Aktive eksperimenter LSP værktøj           |
-| `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Deaktiver filovervågning                   |
-| `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Aktive eksperimenter Exa-funktioner        |
-| `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Aktiver TY LSP for python-filer            |
-| `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Aktiver plantilstand                       |
+| Variabel                                        | Skriv   | Beskrivelse                               |
+| ----------------------------------------------- | ------- | ----------------------------------------- |
+| `OPENCODE_EXPERIMENTAL`                         | boolean | Aktiver alle eksperimentelle funktioner   |
+| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY`          | boolean | Aktiver ikonopdagelse                     |
+| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT`  | boolean | Deaktiver kopi ved valg i TUI             |
+| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | nummer  | Standard timeout for bash-kommandoer i ms |
+| `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX`        | nummer  | Maks. output-tokens for LLM-svar          |
+| `OPENCODE_EXPERIMENTAL_FILEWATCHER`             | boolean | Aktiver filovervågning for hele dir       |
+| `OPENCODE_EXPERIMENTAL_OXFMT`                   | boolean | Aktiver oxfmt formatter                   |
+| `OPENCODE_EXPERIMENTAL_LSP_TOOL`                | boolean | Aktive eksperimenter LSP værktøj          |
+| `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER`     | boolean | Deaktiver filovervågning                  |
+| `OPENCODE_EXPERIMENTAL_EXA`                     | boolean | Aktive eksperimenter Exa-funktioner       |
+| `OPENCODE_EXPERIMENTAL_LSP_TY`                  | boolean | Aktiver TY LSP for python-filer           |
+| `OPENCODE_EXPERIMENTAL_PLAN_MODE`               | boolean | Aktiver plantilstand                      |
diff --git a/packages/web/src/content/docs/zh-cn/cli.mdx b/packages/web/src/content/docs/zh-cn/cli.mdx
index 7ae6a521a..883a8904c 100644
--- a/packages/web/src/content/docs/zh-cn/cli.mdx
+++ b/packages/web/src/content/docs/zh-cn/cli.mdx
@@ -596,7 +596,7 @@ OpenCode 可以通过环境变量进行配置。
 这些环境变量用于启用可能会更改或移除的实验性功能。
 
 | 变量                                            | 类型    | 描述                            |
-| ----------------------------------------------- | ------- | ------------------------------- | --- | -------------------------------- | ------- | ------------------------ |
+| ----------------------------------------------- | ------- | ------------------------------- |
 | `OPENCODE_EXPERIMENTAL`                         | boolean | 启用所有实验性功能              |
 | `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY`          | boolean | 启用图标发现                    |
 | `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT`  | boolean | 禁用 TUI 中的选中即复制         |

From 04286d04159d7dda2d27c1bde7a38f4c275cc1f2 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 21:58:54 -0400
Subject: [PATCH 320/378] docs(effect): plan Instance deletion path (#27424)

---
 packages/opencode/specs/effect/todo.md | 57 ++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/packages/opencode/specs/effect/todo.md b/packages/opencode/specs/effect/todo.md
index 0c10f1227..b0f1869c6 100644
--- a/packages/opencode/specs/effect/todo.md
+++ b/packages/opencode/specs/effect/todo.md
@@ -224,6 +224,63 @@ Goal:
   explicit context where practical.
 - Delete `project/instance.ts` only after ambient Instance coupling is gone.
 
+Important distinction:
+
+- `InstanceState.context`, `InstanceState.directory`, and
+  `InstanceState.workspaceID` are acceptable inside normal Effect service
+  code when `InstanceRef` / `WorkspaceRef` are provided by the runtime.
+- The deletion blockers are the fallback and callback paths that rely on
+  ambient ALS: direct `Instance.*` reads, `InstanceState.bind(...)`,
+  `AppRuntime.runPromise(...)` re-entry from plain JS, and bridge restore
+  code that installs legacy ALS before invoking callbacks.
+
+Current bottom-up inventory from `dev`:
+
+- Direct `Instance.*` value readers:
+  [`tool/repo_overview.ts`](../../src/tool/repo_overview.ts),
+  [`control-plane/adapters/worktree.ts`](../../src/control-plane/adapters/worktree.ts),
+  [`cli/bootstrap.ts`](../../src/cli/bootstrap.ts).
+- `InstanceState.bind(...)` callback boundaries:
+  [`file/watcher.ts`](../../src/file/watcher.ts) native watcher callback,
+  [`storage/db.ts`](../../src/storage/db.ts) transaction/effect callbacks,
+  [`session/llm.ts`](../../src/session/llm.ts) workflow approval callback.
+- `AppRuntime.runPromise(...)` / re-entry from plain JS:
+  [`project/with-instance.ts`](../../src/project/with-instance.ts),
+  [`project/instance-runtime.ts`](../../src/project/instance-runtime.ts),
+  [`control-plane/adapters/worktree.ts`](../../src/control-plane/adapters/worktree.ts),
+  [`cli/effect-cmd.ts`](../../src/cli/effect-cmd.ts), plus global/non-instance
+  callsites such as CLI upgrade and ACP agent defaults.
+- Intentional bridge users to classify, not delete blindly:
+  workspace adapters in [`control-plane/workspace.ts`](../../src/control-plane/workspace.ts),
+  MCP, command execution, plugins, pty lifecycle, bus scope cleanup, task
+  cancellation, and HTTP lifecycle reload/dispose paths.
+- Core fallback layer to shrink last:
+  [`effect/run-service.ts`](../../src/effect/run-service.ts),
+  [`effect/bridge.ts`](../../src/effect/bridge.ts), and
+  [`effect/instance-state.ts`](../../src/effect/instance-state.ts).
+
+Recommended PR order:
+
+- [ ] `INST-1` Remove direct `Instance.*` value readers. Start with
+      `repo_overview`, `worktree` adapter, and `cli/bootstrap`; pass context
+      explicitly or obtain it from an Effect boundary.
+- [ ] `INST-2` Move type-only `InstanceContext` imports from
+      [`project/instance.ts`](../../src/project/instance.ts) to
+      [`project/instance-context.ts`](../../src/project/instance-context.ts).
+- [ ] `INST-3` Audit each `InstanceState.bind(...)` callback from the inside
+      out: list what the callback calls (`Bus.publish`, database effects,
+      permission/session services), then replace ambient capture with explicit
+      `InstanceRef` / `WorkspaceRef` provision or an `EffectBridge` call.
+- [ ] `INST-4` Classify `AppRuntime.runPromise(...)` callsites as global,
+      instance-scoped with explicit refs, or bridge-required. Eliminate the
+      instance-scoped callsites that rely on `run-service.attach()` falling
+      back to `Instance.current`.
+- [ ] `INST-5` After consumers are explicit, remove `Instance.current` fallback
+      from `InstanceState.context` and `run-service.attach()`.
+- [ ] `INST-6` Move any remaining `restore` / `bind` compatibility helpers to
+      the boundary that still needs them, then delete
+      [`project/instance.ts`](../../src/project/instance.ts).
+
 ## Lower Priority Tracks
 
 - `PROC` / `FS` — continue AppProcess and AppFileSystem migrations as

From b928a1fff9e298082bbb0249ae071c133331e7b8 Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 22:02:30 -0400
Subject: [PATCH 321/378] fix(httpapi): preserve event stream context (#27425)

Co-authored-by: Aiden Cline 
Co-authored-by: James Long 
---
 .../server/routes/instance/httpapi/event.ts   | 53 +++++++-----
 .../test/server/httpapi-event.test.ts         | 86 ++++++++++++++++---
 2 files changed, 102 insertions(+), 37 deletions(-)

diff --git a/packages/opencode/src/server/routes/instance/httpapi/event.ts b/packages/opencode/src/server/routes/instance/httpapi/event.ts
index 8113c76f5..05160f55a 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/event.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/event.ts
@@ -40,30 +40,37 @@ function eventData(data: unknown): Sse.Event {
 }
 
 function eventResponse(bus: Bus.Interface) {
-  const events = bus.subscribeAll().pipe(Stream.takeUntil((event) => event.type === Bus.InstanceDisposed.type))
-  const heartbeat = Stream.tick("10 seconds").pipe(
-    Stream.drop(1),
-    Stream.map(() => ({ id: Bus.createID(), type: "server.heartbeat", properties: {} })),
-  )
+  return Effect.gen(function* () {
+    const context = yield* Effect.context()
 
-  log.info("event connected")
-  return HttpServerResponse.stream(
-    Stream.make({ id: Bus.createID(), type: "server.connected", properties: {} }).pipe(
-      Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))),
-      Stream.map(eventData),
-      Stream.pipeThroughChannel(Sse.encode()),
-      Stream.encodeText,
-      Stream.ensuring(Effect.sync(() => log.info("event disconnected"))),
-    ),
-    {
-      contentType: "text/event-stream",
-      headers: {
-        "Cache-Control": "no-cache, no-transform",
-        "X-Accel-Buffering": "no",
-        "X-Content-Type-Options": "nosniff",
+    const events = bus.subscribeAll().pipe(
+      Stream.provideContext(context),
+      Stream.takeUntil((event) => event.type === Bus.InstanceDisposed.type),
+    )
+    const heartbeat = Stream.tick("10 seconds").pipe(
+      Stream.drop(1),
+      Stream.map(() => ({ id: Bus.createID(), type: "server.heartbeat", properties: {} })),
+    )
+
+    log.info("event connected")
+    return HttpServerResponse.stream(
+      Stream.make({ id: Bus.createID(), type: "server.connected", properties: {} }).pipe(
+        Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))),
+        Stream.map(eventData),
+        Stream.pipeThroughChannel(Sse.encode()),
+        Stream.encodeText,
+        Stream.ensuring(Effect.sync(() => log.info("event disconnected"))),
+      ),
+      {
+        contentType: "text/event-stream",
+        headers: {
+          "Cache-Control": "no-cache, no-transform",
+          "X-Accel-Buffering": "no",
+          "X-Content-Type-Options": "nosniff",
+        },
       },
-    },
-  )
+    )
+  })
 }
 
 export const eventHandlers = HttpApiBuilder.group(EventApi, "event", (handlers) =>
@@ -72,7 +79,7 @@ export const eventHandlers = HttpApiBuilder.group(EventApi, "event", (handlers)
     return handlers.handleRaw(
       "subscribe",
       Effect.fn("EventHttpApi.subscribe")(function* () {
-        return eventResponse(bus)
+        return yield* eventResponse(bus)
       }),
     )
   }),
diff --git a/packages/opencode/test/server/httpapi-event.test.ts b/packages/opencode/test/server/httpapi-event.test.ts
index df716ed09..d5680d454 100644
--- a/packages/opencode/test/server/httpapi-event.test.ts
+++ b/packages/opencode/test/server/httpapi-event.test.ts
@@ -1,10 +1,13 @@
 import { afterEach, describe, expect, test } from "bun:test"
+import { Bus } from "../../src/bus"
 import { Instance } from "../../src/project/instance"
 import { Server } from "../../src/server/server"
 import { EventPaths } from "../../src/server/routes/instance/httpapi/event"
+import { Event as ServerEvent } from "../../src/server/event"
 import * as Log from "@opencode-ai/core/util/log"
+import { Schema } from "effect"
 import { resetDatabase } from "../fixture/db"
-import { disposeAllInstances, tmpdir } from "../fixture/fixture"
+import { disposeAllInstances, reloadTestInstance, tmpdir } from "../fixture/fixture"
 
 void Log.init({ print: false })
 
@@ -12,25 +15,42 @@ function app() {
   return Server.Default().app
 }
 
-async function readFirstChunk(response: Response) {
-  if (!response.body) throw new Error("missing response body")
-  const reader = response.body.getReader()
-  const result = await Promise.race([
-    reader.read(),
-    new Promise((_, reject) => setTimeout(() => reject(new Error("timed out waiting for event")), 5_000)),
-  ])
-  await reader.cancel()
-  return new TextDecoder().decode(result.value)
+const EventData = Schema.Struct({
+  id: Schema.optional(Schema.String),
+  type: Schema.String,
+  properties: Schema.Record(Schema.String, Schema.Any),
+})
+
+async function readChunk(reader: ReadableStreamDefaultReader) {
+  let timeout: ReturnType | undefined
+  try {
+    return await Promise.race([
+      reader.read(),
+      new Promise((_, reject) => {
+        timeout = setTimeout(() => reject(new Error("timed out waiting for event")), 5_000)
+      }),
+    ])
+  } finally {
+    if (timeout) clearTimeout(timeout)
+  }
 }
 
 async function readFirstEvent(response: Response) {
-  return JSON.parse((await readFirstChunk(response)).replace(/^data: /, "")) as {
-    id?: string
-    type: string
-    properties: Record
+  if (!response.body) throw new Error("missing response body")
+  const reader = response.body.getReader()
+  try {
+    return await readEvent(reader)
+  } finally {
+    await reader.cancel()
   }
 }
 
+async function readEvent(reader: ReadableStreamDefaultReader) {
+  const result = await readChunk(reader)
+  if (result.done || !result.value) throw new Error("event stream closed")
+  return Schema.decodeUnknownSync(EventData)(JSON.parse(new TextDecoder().decode(result.value).replace(/^data: /, "")))
+}
+
 afterEach(async () => {
   await disposeAllInstances()
   await resetDatabase()
@@ -56,4 +76,42 @@ describe("event HttpApi", () => {
 
     expect(await readFirstEvent(response)).toMatchObject({ type: "server.connected", properties: {} })
   })
+
+  test("keeps the event stream open after the initial event", async () => {
+    await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
+    const response = await app().request(EventPaths.event, { headers: { "x-opencode-directory": tmp.path } })
+    if (!response.body) throw new Error("missing response body")
+
+    const reader = response.body.getReader()
+    try {
+      expect(await readEvent(reader)).toMatchObject({ type: "server.connected", properties: {} })
+      const next = await Promise.race([
+        reader.read().then((result) => (result.done ? "closed" : "event")),
+        new Promise<"open">((resolve) => setTimeout(() => resolve("open"), 250)),
+      ])
+
+      expect(next).toBe("open")
+    } finally {
+      await reader.cancel()
+    }
+  })
+
+  test("delivers instance bus events after the initial event", async () => {
+    await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
+    const response = await app().request(EventPaths.event, { headers: { "x-opencode-directory": tmp.path } })
+    if (!response.body) throw new Error("missing response body")
+
+    const reader = response.body.getReader()
+    try {
+      expect(await readEvent(reader)).toMatchObject({ type: "server.connected", properties: {} })
+
+      const next = readEvent(reader)
+      const ctx = await reloadTestInstance({ directory: tmp.path })
+      await Instance.restore(ctx, () => Bus.publish(ServerEvent.Connected, {}))
+
+      expect(await next).toMatchObject({ type: "server.connected", properties: {} })
+    } finally {
+      await reader.cancel()
+    }
+  })
 })

From cda8cc72851e956e5c705b57819d80658adf2a0e Mon Sep 17 00:00:00 2001
From: Kit Langton 
Date: Wed, 13 May 2026 22:18:40 -0400
Subject: [PATCH 322/378] test(httpapi): simplify event stream regression
 coverage (#27427)

---
 .../test/server/httpapi-event.test.ts         | 29 ++++++++++---------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/packages/opencode/test/server/httpapi-event.test.ts b/packages/opencode/test/server/httpapi-event.test.ts
index d5680d454..3e1547ebf 100644
--- a/packages/opencode/test/server/httpapi-event.test.ts
+++ b/packages/opencode/test/server/httpapi-event.test.ts
@@ -51,6 +51,20 @@ async function readEvent(reader: ReadableStreamDefaultReader) {
   return Schema.decodeUnknownSync(EventData)(JSON.parse(new TextDecoder().decode(result.value).replace(/^data: /, "")))
 }
 
+async function readStatusWithin(reader: ReadableStreamDefaultReader, delay: number) {
+  let timeout: ReturnType | undefined
+  try {
+    return await Promise.race([
+      reader.read().then((result) => (result.done ? "closed" : "event")),
+      new Promise<"open">((resolve) => {
+        timeout = setTimeout(() => resolve("open"), delay)
+      }),
+    ])
+  } finally {
+    if (timeout) clearTimeout(timeout)
+  }
+}
+
 afterEach(async () => {
   await disposeAllInstances()
   await resetDatabase()
@@ -69,14 +83,6 @@ describe("event HttpApi", () => {
     expect(await readFirstEvent(response)).toMatchObject({ type: "server.connected", properties: {} })
   })
 
-  test("serves the initial server connected event", async () => {
-    await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
-    const headers = { "x-opencode-directory": tmp.path }
-    const response = await app().request(EventPaths.event, { headers })
-
-    expect(await readFirstEvent(response)).toMatchObject({ type: "server.connected", properties: {} })
-  })
-
   test("keeps the event stream open after the initial event", async () => {
     await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
     const response = await app().request(EventPaths.event, { headers: { "x-opencode-directory": tmp.path } })
@@ -85,12 +91,7 @@ describe("event HttpApi", () => {
     const reader = response.body.getReader()
     try {
       expect(await readEvent(reader)).toMatchObject({ type: "server.connected", properties: {} })
-      const next = await Promise.race([
-        reader.read().then((result) => (result.done ? "closed" : "event")),
-        new Promise<"open">((resolve) => setTimeout(() => resolve("open"), 250)),
-      ])
-
-      expect(next).toBe("open")
+      expect(await readStatusWithin(reader, 250)).toBe("open")
     } finally {
       await reader.cancel()
     }

From ddad0988e745fc298ba4b3ec00563a202fa99a66 Mon Sep 17 00:00:00 2001
From: opencode 
Date: Thu, 14 May 2026 03:03:23 +0000
Subject: [PATCH 323/378] sync release versions for v1.14.50

---
 bun.lock                               | 34 +++++++++++++-------------
 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/core/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/http-recorder/package.json    |  2 +-
 packages/llm/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/web/package.json              |  2 +-
 sdks/vscode/package.json               |  2 +-
 20 files changed, 41 insertions(+), 41 deletions(-)

diff --git a/bun.lock b/bun.lock
index 3fafe9d5e..51fdda48e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -29,7 +29,7 @@
     },
     "packages/app": {
       "name": "@opencode-ai/app",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/core": "workspace:*",
@@ -84,7 +84,7 @@
     },
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
@@ -119,7 +119,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -146,7 +146,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@ai-sdk/anthropic": "3.0.64",
         "@ai-sdk/openai": "3.0.48",
@@ -168,7 +168,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -192,7 +192,7 @@
     },
     "packages/core": {
       "name": "@opencode-ai/core",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -253,7 +253,7 @@
     },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "drizzle-orm": "catalog:",
         "effect": "catalog:",
@@ -307,7 +307,7 @@
     },
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@opencode-ai/core": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
@@ -337,7 +337,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "catalog:",
@@ -353,7 +353,7 @@
     },
     "packages/http-recorder": {
       "name": "@opencode-ai/http-recorder",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@effect/platform-node": "catalog:",
         "effect": "catalog:",
@@ -366,7 +366,7 @@
     },
     "packages/llm": {
       "name": "@opencode-ai/llm",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@smithy/eventstream-codec": "4.2.14",
         "@smithy/util-utf8": "4.2.2",
@@ -384,7 +384,7 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -520,7 +520,7 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "effect": "catalog:",
@@ -558,7 +558,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "cross-spawn": "catalog:",
       },
@@ -573,7 +573,7 @@
     },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -608,7 +608,7 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/core": "workspace:*",
@@ -657,7 +657,7 @@
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.14.49",
+      "version": "1.14.50",
       "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 20d417b01..c371427e7 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/app",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "description": "",
   "type": "module",
   "exports": {
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index 1ae0956c1..8a906c102 100644
--- a/packages/console/app/package.json
+++ b/packages/console/app/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-app",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "type": "module",
   "license": "MIT",
   "scripts": {
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index 3eaac7913..e8585f20d 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.14.49",
+  "version": "1.14.50",
   "private": true,
   "type": "module",
   "license": "MIT",
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index 7c05c1e19..3ebebd2b9 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-function",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "$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 4f3a61cbd..baac89ebf 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-mail",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",
diff --git a/packages/core/package.json b/packages/core/package.json
index 0551957f0..ab540238b 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "name": "@opencode-ai/core",
   "type": "module",
   "license": "MIT",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index d9d7aa0bd..978997228 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop",
   "private": true,
-  "version": "1.14.49",
+  "version": "1.14.50",
   "type": "module",
   "license": "MIT",
   "homepage": "https://opencode.ai",
diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json
index 846f57093..bad16ab0b 100644
--- a/packages/enterprise/package.json
+++ b/packages/enterprise/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/enterprise",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "private": true,
   "type": "module",
   "license": "MIT",
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index e4ac34b07..782a66409 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.14.49"
+version = "1.14.50"
 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.14.49/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.50/opencode-darwin-arm64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.49/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.50/opencode-darwin-x64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.49/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.50/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.14.49/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.50/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.14.49/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.50/opencode-windows-x64.zip"
 cmd = "./opencode.exe"
 args = ["acp"]
diff --git a/packages/function/package.json b/packages/function/package.json
index 107d3eca7..00350005b 100644
--- a/packages/function/package.json
+++ b/packages/function/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/function",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",
diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json
index 807d248c4..ab22d6910 100644
--- a/packages/http-recorder/package.json
+++ b/packages/http-recorder/package.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "name": "@opencode-ai/http-recorder",
   "type": "module",
   "license": "MIT",
diff --git a/packages/llm/package.json b/packages/llm/package.json
index a65f879a1..f68b262f0 100644
--- a/packages/llm/package.json
+++ b/packages/llm/package.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "name": "@opencode-ai/llm",
   "type": "module",
   "license": "MIT",
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index ba7b22c69..564b2eb1c 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "name": "opencode",
   "type": "module",
   "license": "MIT",
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index a661c0f6b..fb794c026 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.14.49",
+  "version": "1.14.50",
   "type": "module",
   "license": "MIT",
   "scripts": {
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index d7c974ed6..b021dfce4 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.14.49",
+  "version": "1.14.50",
   "type": "module",
   "license": "MIT",
   "scripts": {
diff --git a/packages/slack/package.json b/packages/slack/package.json
index 5d84fed54..f6a14b46f 100644
--- a/packages/slack/package.json
+++ b/packages/slack/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/slack",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "type": "module",
   "license": "MIT",
   "scripts": {
diff --git a/packages/ui/package.json b/packages/ui/package.json
index f4aca0d68..dda7cb6b6 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/ui",
-  "version": "1.14.49",
+  "version": "1.14.50",
   "type": "module",
   "license": "MIT",
   "exports": {
diff --git a/packages/web/package.json b/packages/web/package.json
index 36df1907e..448e0e0e5 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.14.49",
+  "version": "1.14.50",
   "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 0eeafe257..217251b39 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.14.49",
+  "version": "1.14.50",
   "publisher": "sst-dev",
   "repository": {
     "type": "git",

From c50d2b3656f7e50730977752284a7bba8711c72e Mon Sep 17 00:00:00 2001
From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Date: Wed, 13 May 2026 22:41:17 -0500
Subject: [PATCH 324/378] Refactor event HTTP API route modules (#27441)

---
 .../src/server/routes/instance/httpapi/api.ts |  2 +-
 .../routes/instance/httpapi/groups/event.ts   | 24 +++++++++++++++++
 .../instance/httpapi/{ => handlers}/event.ts  | 27 +++----------------
 .../server/routes/instance/httpapi/server.ts  |  3 ++-
 .../test/server/httpapi-event.test.ts         |  2 +-
 .../server/httpapi-raw-route-auth.test.ts     |  2 +-
 6 files changed, 32 insertions(+), 28 deletions(-)
 create mode 100644 packages/opencode/src/server/routes/instance/httpapi/groups/event.ts
 rename packages/opencode/src/server/routes/instance/httpapi/{ => handlers}/event.ts (68%)

diff --git a/packages/opencode/src/server/routes/instance/httpapi/api.ts b/packages/opencode/src/server/routes/instance/httpapi/api.ts
index 4c6e46a45..eff336b3c 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/api.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/api.ts
@@ -4,7 +4,7 @@ import { BusEvent } from "@/bus/bus-event"
 import { SyncEvent } from "@/sync"
 import { ConfigApi } from "./groups/config"
 import { ControlApi } from "./groups/control"
-import { EventApi } from "./event"
+import { EventApi } from "./groups/event"
 import { ExperimentalApi } from "./groups/experimental"
 import { FileApi } from "./groups/file"
 import { GlobalApi } from "./groups/global"
diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/event.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/event.ts
new file mode 100644
index 000000000..7ebc229ee
--- /dev/null
+++ b/packages/opencode/src/server/routes/instance/httpapi/groups/event.ts
@@ -0,0 +1,24 @@
+import { Schema } from "effect"
+import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
+import { WorkspaceRoutingQuery } from "../middleware/workspace-routing"
+
+export const EventPaths = {
+  event: "/event",
+} as const
+
+export const EventApi = HttpApi.make("event").add(
+  HttpApiGroup.make("event")
+    .add(
+      HttpApiEndpoint.get("subscribe", EventPaths.event, {
+        query: WorkspaceRoutingQuery,
+        success: Schema.String.pipe(HttpApiSchema.asText({ contentType: "text/event-stream" })),
+      }).annotateMerge(
+        OpenApi.annotations({
+          identifier: "event.subscribe",
+          summary: "Subscribe to events",
+          description: "Get events",
+        }),
+      ),
+    )
+    .annotateMerge(OpenApi.annotations({ title: "event", description: "Instance event stream route." })),
+)
diff --git a/packages/opencode/src/server/routes/instance/httpapi/event.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts
similarity index 68%
rename from packages/opencode/src/server/routes/instance/httpapi/event.ts
rename to packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts
index 05160f55a..c0bcbc82c 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/event.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts
@@ -1,35 +1,14 @@
 import { Bus } from "@/bus"
 import * as Log from "@opencode-ai/core/util/log"
-import { Effect, Schema } from "effect"
+import { Effect } from "effect"
 import * as Stream from "effect/Stream"
 import { HttpServerResponse } from "effect/unstable/http"
-import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
+import { HttpApiBuilder } from "effect/unstable/httpapi"
 import * as Sse from "effect/unstable/encoding/Sse"
-import { WorkspaceRoutingQuery } from "./middleware/workspace-routing"
+import { EventApi } from "../groups/event"
 
 const log = Log.create({ service: "server" })
 
-export const EventPaths = {
-  event: "/event",
-} as const
-
-export const EventApi = HttpApi.make("event").add(
-  HttpApiGroup.make("event")
-    .add(
-      HttpApiEndpoint.get("subscribe", EventPaths.event, {
-        query: WorkspaceRoutingQuery,
-        success: Schema.String.pipe(HttpApiSchema.asText({ contentType: "text/event-stream" })),
-      }).annotateMerge(
-        OpenApi.annotations({
-          identifier: "event.subscribe",
-          summary: "Subscribe to events",
-          description: "Get events",
-        }),
-      ),
-    )
-    .annotateMerge(OpenApi.annotations({ title: "event", description: "Instance event stream route." })),
-)
-
 function eventData(data: unknown): Sse.Event {
   return {
     _tag: "Event",
diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts
index 84feef6b0..cac635132 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/server.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts
@@ -58,7 +58,8 @@ import { ServerAuth } from "@/server/auth"
 import { InstanceHttpApi, RootHttpApi } from "./api"
 import { PublicApi } from "./public"
 import { authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
-import { EventApi, eventHandlers } from "./event"
+import { EventApi } from "./groups/event"
+import { eventHandlers } from "./handlers/event"
 import { configHandlers } from "./handlers/config"
 import { controlHandlers } from "./handlers/control"
 import { experimentalHandlers } from "./handlers/experimental"
diff --git a/packages/opencode/test/server/httpapi-event.test.ts b/packages/opencode/test/server/httpapi-event.test.ts
index 3e1547ebf..3f1d1e114 100644
--- a/packages/opencode/test/server/httpapi-event.test.ts
+++ b/packages/opencode/test/server/httpapi-event.test.ts
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test"
 import { Bus } from "../../src/bus"
 import { Instance } from "../../src/project/instance"
 import { Server } from "../../src/server/server"
-import { EventPaths } from "../../src/server/routes/instance/httpapi/event"
+import { EventPaths } from "../../src/server/routes/instance/httpapi/groups/event"
 import { Event as ServerEvent } from "../../src/server/event"
 import * as Log from "@opencode-ai/core/util/log"
 import { Schema } from "effect"
diff --git a/packages/opencode/test/server/httpapi-raw-route-auth.test.ts b/packages/opencode/test/server/httpapi-raw-route-auth.test.ts
index b1d4af76b..b75e11484 100644
--- a/packages/opencode/test/server/httpapi-raw-route-auth.test.ts
+++ b/packages/opencode/test/server/httpapi-raw-route-auth.test.ts
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test"
 import { ConfigProvider, Layer } from "effect"
 import { HttpRouter } from "effect/unstable/http"
 import { Instance } from "../../src/project/instance"
-import { EventPaths } from "../../src/server/routes/instance/httpapi/event"
+import { EventPaths } from "../../src/server/routes/instance/httpapi/groups/event"
 import { PtyPaths } from "../../src/server/routes/instance/httpapi/groups/pty"
 import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
 import { PtyID } from "../../src/pty/schema"

From 981e00971a803f1c753f82a1a399ff4d56297a5c Mon Sep 17 00:00:00 2001
From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Date: Wed, 13 May 2026 23:06:07 -0500
Subject: [PATCH 325/378] fix: image resizer wasm loading, reenable image
 resizing (#26805)

---
 packages/opencode/src/config/attachment.ts    |   2 +-
 packages/opencode/src/image/image.ts          |  60 ++--
 packages/opencode/src/session/processor.ts    |  18 +-
 packages/opencode/src/session/prompt.ts       |  58 ++--
 .../image/fixtures/picture-5mb-base64.png     | Bin 0 -> 3932160 bytes
 packages/opencode/test/image/image.test.ts    |  41 +++
 packages/web/src/content/docs/config.mdx      |  29 ++
 .../@silvia-odwyer%2Fphoton-node@0.3.4.patch  | 277 +++++++++++++++++-
 8 files changed, 419 insertions(+), 66 deletions(-)
 create mode 100644 packages/opencode/test/image/fixtures/picture-5mb-base64.png

diff --git a/packages/opencode/src/config/attachment.ts b/packages/opencode/src/config/attachment.ts
index a5fc59973..80e44bc2e 100644
--- a/packages/opencode/src/config/attachment.ts
+++ b/packages/opencode/src/config/attachment.ts
@@ -14,7 +14,7 @@ export const Image = Schema.Struct({
     description: "Maximum image height before resizing or rejecting the attachment (default: 2000)",
   }),
   max_base64_bytes: Schema.optional(PositiveInt).annotate({
-    description: "Maximum base64 payload bytes for an image attachment (default: 4718592)",
+    description: "Maximum base64 payload bytes for an image attachment (default: 5242880)",
   }),
 }).annotate({ identifier: "ImageAttachmentConfig" })
 export type Image = Schema.Schema.Type
diff --git a/packages/opencode/src/image/image.ts b/packages/opencode/src/image/image.ts
index 2115e1919..2a3c4fa5c 100644
--- a/packages/opencode/src/image/image.ts
+++ b/packages/opencode/src/image/image.ts
@@ -1,21 +1,24 @@
 import { Config } from "@/config/config"
 import type { MessageV2 } from "@/session/message-v2"
 import * as Log from "@opencode-ai/core/util/log"
+import photonWasm from "@silvia-odwyer/photon-node/photon_rs_bg.wasm" with { type: "file" }
 import { Context, Effect, Layer, Schema } from "effect"
+import path from "node:path"
+import { fileURLToPath } from "node:url"
 
-const MAX_BASE64_BYTES = 4.5 * 1024 * 1024
+const MAX_BASE64_BYTES = 5 * 1024 * 1024
 const MAX_WIDTH = 2000
 const MAX_HEIGHT = 2000
 const AUTO_RESIZE = true
 const JPEG_QUALITIES = [80, 85, 70, 55, 40]
 const log = Log.create({ service: "image" })
 
-export class PhotonUnavailableError extends Schema.TaggedErrorClass()(
-  "ImagePhotonUnavailableError",
+export class ResizerUnavailableError extends Schema.TaggedErrorClass()(
+  "ImageResizerUnavailableError",
   {},
 ) {
   override get message() {
-    return "Photon image processor is unavailable"
+    return "Image resizer is unavailable"
   }
 }
 
@@ -46,7 +49,7 @@ export class SizeError extends Schema.TaggedErrorClass()("ImageSizeEr
   }
 }
 
-export type Error = PhotonUnavailableError | InvalidDataUrlError | DecodeError | SizeError
+export type Error = ResizerUnavailableError | InvalidDataUrlError | DecodeError | SizeError
 
 export interface Interface {
   readonly normalize: (input: MessageV2.FilePart) => Effect.Effect
@@ -59,18 +62,15 @@ export const layer = Layer.effect(
   Effect.gen(function* () {
     const config = yield* Config.Service
     const loadPhoton = yield* Effect.cached(
-      Effect.promise(async () => {
-        try {
-          const photonWasm = (await import("@silvia-odwyer/photon-node/photon_rs_bg.wasm", { with: { type: "file" } }))
-            .default
-          // Patched photon-node reads this during module init so Bun compiled binaries use the embedded wasm path.
-          ;(globalThis as typeof globalThis & { __OPENCODE_PHOTON_WASM_PATH?: string }).__OPENCODE_PHOTON_WASM_PATH =
-            photonWasm
-          return await import("@silvia-odwyer/photon-node")
-        } catch {
-          return null
-        }
-      }),
+      Effect.sync(() => {
+        // Patched photon-node reads this during module init so Bun compiled binaries use the embedded wasm path.
+        ;(globalThis as typeof globalThis & { __OPENCODE_PHOTON_WASM_PATH?: string }).__OPENCODE_PHOTON_WASM_PATH =
+          path.isAbsolute(photonWasm) ? photonWasm : fileURLToPath(new URL(photonWasm, import.meta.url))
+      }).pipe(
+        Effect.andThen(() => Effect.tryPromise(() => import("@silvia-odwyer/photon-node"))),
+        Effect.tapError((error) => Effect.sync(() => log.warn("failed to load photon", { error }))),
+        Effect.mapError(() => new ResizerUnavailableError()),
+      ),
     )
 
     const normalize = Effect.fn("Image.normalize")(function* (input: MessageV2.FilePart) {
@@ -85,30 +85,26 @@ export const layer = Layer.effect(
         return yield* new InvalidDataUrlError({ url: input.url })
 
       const base64 = input.url.slice(input.url.indexOf(";base64,") + ";base64,".length)
+      const bytes = Buffer.byteLength(base64, "utf8")
+
       const photon = yield* loadPhoton
-      if (!photon) return yield* new PhotonUnavailableError()
 
-      const decoded = yield* Effect.sync(() => {
-        try {
-          return photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64"))
-        } catch {
-          return undefined
-        }
+      const decoded = yield* Effect.try({
+        try: () => photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64")),
+        catch: (error) => {
+          log.warn("failed to decode image", { error })
+          return new DecodeError()
+        },
       })
-      if (!decoded) return yield* new DecodeError()
 
       try {
         const originalWidth = decoded.get_width()
         const originalHeight = decoded.get_height()
-        if (
-          originalWidth <= info.maxWidth &&
-          originalHeight <= info.maxHeight &&
-          Buffer.byteLength(base64, "utf8") <= info.maxBase64Bytes
-        )
+        if (originalWidth <= info.maxWidth && originalHeight <= info.maxHeight && bytes <= info.maxBase64Bytes)
           return input
         if (!info.autoResize)
           return yield* new SizeError({
-            bytes: Buffer.byteLength(base64, "utf8"),
+            bytes,
             max: info.maxBase64Bytes,
             width: originalWidth,
             height: originalHeight,
@@ -159,7 +155,7 @@ export const layer = Layer.effect(
         }
 
         return yield* new SizeError({
-          bytes: Buffer.byteLength(base64, "utf8"),
+          bytes,
           max: info.maxBase64Bytes,
           width: originalWidth,
           height: originalHeight,
diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts
index 9765175e9..7ba9631e6 100644
--- a/packages/opencode/src/session/processor.ts
+++ b/packages/opencode/src/session/processor.ts
@@ -405,14 +405,18 @@ export const layer: Layer.Layer<
                 typeof attachment.mime === "string" &&
                 typeof attachment.url === "string",
             )
-            // temporarily disabled
-            // const normalized = yield* Effect.forEach(toolAttachments, (attachment) =>
-            //   attachment.mime.startsWith("image/")
-            //     ? image.normalize(attachment).pipe(Effect.exit)
-            //     : Effect.succeed(Exit.succeed(attachment)),
-            // )
             const normalized = yield* Effect.forEach(toolAttachments, (attachment) =>
-              Effect.succeed(Exit.succeed(attachment)),
+              attachment.mime.startsWith("image/")
+                ? image
+                    .normalize(attachment)
+                    .pipe(
+                      Effect.catchIf(
+                        (error) => error instanceof Image.ResizerUnavailableError,
+                        () => Effect.succeed(attachment),
+                      ),
+                      Effect.exit,
+                    )
+                : Effect.succeed(Exit.succeed(attachment)),
             )
             const omitted = normalized.filter(Exit.isFailure).length
             const attachments = normalized.filter(Exit.isSuccess).map((item) => item.value)
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 12a9e1a8b..6248ce455 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -42,6 +42,7 @@ import { Shell } from "@/shell/shell"
 import { ShellID } from "@/tool/shell/id"
 import { AppFileSystem } from "@opencode-ai/core/filesystem"
 import { Truncate } from "@/tool/truncate"
+import { Image } from "@/image/image"
 import { decodeDataUrl } from "@/util/data-url"
 import { Process } from "@/util/process"
 import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect"
@@ -165,10 +166,10 @@ function referenceTextPart(input: {
 
 export interface Interface {
   readonly cancel: (sessionID: SessionID) => Effect.Effect
-  readonly prompt: (input: PromptInput) => Effect.Effect
+  readonly prompt: (input: PromptInput) => Effect.Effect
   readonly loop: (input: LoopInput) => Effect.Effect
   readonly shell: (input: ShellInput) => Effect.Effect
-  readonly command: (input: CommandInput) => Effect.Effect
+  readonly command: (input: CommandInput) => Effect.Effect
   readonly resolvePromptParts: (template: string) => Effect.Effect
 }
 
@@ -193,6 +194,7 @@ export const layer = Layer.effect(
     const lsp = yield* LSP.Service
     const registry = yield* ToolRegistry.Service
     const truncate = yield* Truncate.Service
+    const image = yield* Image.Service
     const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
     const scope = yield* Scope.Scope
     const instruction = yield* Instruction.Service
@@ -211,7 +213,7 @@ export const layer = Layer.effect(
       return {
         cancel: (sessionID: SessionID) => cancel(sessionID),
         resolvePromptParts: (template: string) => resolvePromptParts(template),
-        prompt: (input: PromptInput) => prompt(input),
+        prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)),
       } satisfies TaskPromptOps
     })
 
@@ -1478,7 +1480,16 @@ NOTE: At any point in time through this workflow you should feel free to ask the
         { message: info, parts: resolvedParts },
       )
 
-      const parts = resolvedParts
+      const parts = yield* Effect.forEach(resolvedParts, (part) =>
+        part.type === "file" && part.mime.startsWith("image/")
+          ? image.normalize(part).pipe(
+              Effect.catchIf(
+                (error) => error instanceof Image.ResizerUnavailableError,
+                () => Effect.succeed(part),
+              ),
+            )
+          : Effect.succeed(part),
+      )
 
       const parsed = decodeMessageInfo(info, { errors: "all", propertyOrder: "original" })
       if (Exit.isFailure(parsed)) {
@@ -1599,26 +1610,26 @@ NOTE: At any point in time through this workflow you should feel free to ask the
       return { info, parts }
     }, Effect.scoped)
 
-    const prompt: (input: PromptInput) => Effect.Effect = Effect.fn("SessionPrompt.prompt")(
-      function* (input: PromptInput) {
-        const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
-        yield* revert.cleanup(session)
-        const message = yield* createUserMessage(input)
-        yield* sessions.touch(input.sessionID)
-
-        const permissions: Permission.Ruleset = []
-        for (const [t, enabled] of Object.entries(input.tools ?? {})) {
-          permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
-        }
-        if (permissions.length > 0) {
-          session.permission = permissions
-          yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
-        }
+    const prompt: (input: PromptInput) => Effect.Effect = Effect.fn(
+      "SessionPrompt.prompt",
+    )(function* (input: PromptInput) {
+      const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
+      yield* revert.cleanup(session)
+      const message = yield* createUserMessage(input)
+      yield* sessions.touch(input.sessionID)
+
+      const permissions: Permission.Ruleset = []
+      for (const [t, enabled] of Object.entries(input.tools ?? {})) {
+        permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
+      }
+      if (permissions.length > 0) {
+        session.permission = permissions
+        yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
+      }
 
-        if (input.noReply === true) return message
-        return yield* loop({ sessionID: input.sessionID })
-      },
-    )
+      if (input.noReply === true) return message
+      return yield* loop({ sessionID: input.sessionID })
+    })
 
     const lastAssistant = Effect.fnUntraced(function* (sessionID: SessionID) {
       const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user").pipe(Effect.orDie)
@@ -2019,6 +2030,7 @@ export const defaultLayer = Layer.suspend(() =>
     Layer.provide(Session.defaultLayer),
     Layer.provide(SessionRevert.defaultLayer),
     Layer.provide(SessionSummary.defaultLayer),
+    Layer.provide(Image.defaultLayer),
     Layer.provide(
       Layer.mergeAll(
         Agent.defaultLayer,
diff --git a/packages/opencode/test/image/fixtures/picture-5mb-base64.png b/packages/opencode/test/image/fixtures/picture-5mb-base64.png
new file mode 100644
index 0000000000000000000000000000000000000000..e8923b9e71c752c09dcd6f0833fa2dd40981ee03
GIT binary patch
literal 3932160
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

B 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

opiUu+C{ zG+@x6(_N$5PDz#i+UT|8`-If()~&&^Wy_*RXLlm4e#B2xOnBP%scA5T6fxK)ko$CpBH-P@}A|1 z)8ZzS4k}f=-spb)_?VHiZYHj7oMqGHJxh`v zbe-HiO{=i0#Y>&-8Nn;Q?&1cL7|C{BKs`7xZ=Q`{dcPOTr<0FTxh!DR}d{nSe<3`Po-pO9Caj)8IcMZN9d-hVDH@3#^FFn3;XyVvojT<*^ej#E) z^!z5}7WMggckiQz&KJDf>DnhXj^Ew6pk-XmKJo3>77lwnG;{v5M_NxCRdvXt7m6=f zT{dOfY~PRmWpL%t)I$dR(=zaP1!i6L%mtDwCxwlT{ie~q1BZIQQY0d@<)c3@?hM}i zLybJ!u1>kQbM(qCG25emtavxNXZIufK0Q%?dijXM^A@(fQvcJNs~Y!cPi7<;Dpc>5`fB36-C<=@Y^xI)GUDGmhbx)yMFrmc_j^-UEE_lV z-kQrpdStDg>ePfbqjQxhbTRmoiKFvY>RUd`sbvMq4PD)9`syz(+@4tW`oMZ0fBf<7 zUzWB_s!{&d;hB5(?5Vf6*&82zo_sFiUW?C)-F+V>YFtbyqhsn2>eDl>GOO|X&T>Efd?=SD&%v7}3 zuGQJf_k8!{gdDvRR%Z$HyuZuvLhVAqO`p0@Ep?B7`QH7H>(;u#^f#WqP`t>heD}i- zENIyI>xi%-jV4by_tVz;%{P6wIKFkljT4!39$c_JcK@$oamgWKA{b(EU4GXO(DwDo1epPa+P@9`p9)OQTo4_*~Bn^JWj-wr%a00q3vuiCK`;I9J8q zzqaVRaQXSYZHoT7vh3@}0uc!xr+a-^)ioptj?I6lb(cDu zhJLcJ_mL^n3V-@;m4BzQtK4s0n@k0^S59g(=F06Krag6GO3Af{TMmBhU#~9z^H$kb zr}FgGSx?mrJiHs3@x{NpfBtDft=s*nr!K6y*COyxx!9n8`m(=$&;E-tXDkr6Ie+E? zaVdT%`0v$p&yEZ4S2;Ae;*nJ`d9r7%J#}2n@!xO6?b#oQ;VSVjwiW+4rn;j7=WyZP zfoqfgz2krKsSX4CRZcqA@5|KxULk+x*>N-1MLqfLi?^qh_;z{fC(6wKvum|ax_0Q; zvVBO07A*^R{GvsRHtkzK@uD9B1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5*Bk9O#o2mlBG0H8)XI0lQ=s(?uY zfn=nE^*_QJFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r4EznmHG6n! IY5AnFFE|Ez$^ZZW literal 0 HcmV?d00001 diff --git a/packages/opencode/test/image/image.test.ts b/packages/opencode/test/image/image.test.ts index bf5c0b394..c5d832cd5 100644 --- a/packages/opencode/test/image/image.test.ts +++ b/packages/opencode/test/image/image.test.ts @@ -2,6 +2,7 @@ import { describe, expect } from "bun:test" import { Cause, Effect, Exit, Layer } from "effect" import { Image } from "@/image/image" import { MessageID, PartID, SessionID } from "@/session/schema" +import path from "node:path" import { TestConfig } from "../fixture/config" import { testEffect } from "../lib/effect" @@ -57,6 +58,46 @@ describe("Image", () => { }), ) + it.effect("resizes images that fit the byte limit but exceed dimension limits", () => + Effect.gen(function* () { + const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node")) + const source = new photon.PhotonImage(new Uint8Array(Array.from({ length: 9_000 * 4 }, () => 255)), 9_000, 1) + const image = yield* Image.Service + const result = yield* image.normalize(part("image/png", Buffer.from(source.get_bytes()).toString("base64"))) + const resized = photon.PhotonImage.new_from_byteslice( + Buffer.from(result.url.slice(result.url.indexOf(";base64,") + ";base64,".length), "base64"), + ) + + source.free() + expect(resized.get_width()).toBeLessThanOrEqual(2_000) + expect(resized.get_height()).toBeLessThanOrEqual(2_000) + resized.free() + }), + ) + + it.effect("resizes the 5MB base64 picture fixture", () => + Effect.gen(function* () { + const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node")) + const data = Buffer.from( + yield* Effect.promise(() => + Bun.file(path.join(import.meta.dir, "fixtures", "picture-5mb-base64.png")).arrayBuffer(), + ), + ) + const input = part("image/png", data.toString("base64")) + const image = yield* Image.Service + const result = yield* image.normalize(input) + const base64 = result.url.slice(result.url.indexOf(";base64,") + ";base64,".length) + const resized = photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64")) + + expect(input.url.slice(input.url.indexOf(";base64,") + ";base64,".length).length).toBe(5 * 1024 * 1024) + expect(result.url).not.toBe(input.url) + expect(base64.length).toBeLessThan(5 * 1024 * 1024) + expect(resized.get_width()).toBeLessThanOrEqual(2_000) + expect(resized.get_height()).toBeLessThanOrEqual(2_000) + resized.free() + }), + ) + tiny.effect("fails with a typed size error when no resized candidate fits", () => Effect.gen(function* () { const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node")) diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index 1af77a06e..3cb9d9374 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -393,6 +393,35 @@ You can also configure [local models](/docs/models#local). [Learn more](/docs/mo --- +### Image attachments + +OpenCode normalizes image attachments before sending them to the model. By default, images are resized when they exceed `2000x2000` pixels or `5242880` base64 bytes. + +Configure image attachment limits with the `attachment.image` option: + +```json title="opencode.json" +{ + "$schema": "https://opencode.ai/config.json", + "attachment": { + "image": { + "auto_resize": true, + "max_width": 2000, + "max_height": 2000, + "max_base64_bytes": 5242880 + } + } +} +``` + +- `auto_resize` - Resize images that exceed the configured limits before provider requests. Set to `false` to reject oversized images instead. +- `max_width` - Maximum image width in pixels before resizing or rejection. +- `max_height` - Maximum image height in pixels before resizing or rejection. +- `max_base64_bytes` - Maximum encoded image payload size. This is the base64 payload size, not the original file size. + +If an image still cannot fit after resizing, OpenCode omits oversized tool-result images or fails oversized user-provided images with an image size error. + +--- + #### Provider-Specific Options Some providers support additional configuration options beyond the generic `timeout` and `apiKey` settings. diff --git a/patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch b/patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch index 2e4322556..f7267890a 100644 --- a/patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch +++ b/patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch @@ -1,11 +1,282 @@ diff --git a/photon_rs.js b/photon_rs.js -index 8f4144d..b83e9a9 100644 +index 8f4144d..29a964a 100644 --- a/photon_rs.js +++ b/photon_rs.js -@@ -4509,7 +4509,8 @@ module.exports.__wbindgen_init_externref_table = function() { - ; +@@ -1,6 +1,7 @@ + + let imports = {}; +-imports['__wbindgen_placeholder__'] = module.exports; ++const __wbindgen_placeholder__ = {}; ++imports['__wbindgen_placeholder__'] = __wbindgen_placeholder__; + let wasm; + const { TextEncoder, TextDecoder } = require(`util`); + +@@ -4272,12 +4273,12 @@ class Rgba { + } + module.exports.Rgba = Rgba; + +-module.exports.__wbg_new_abda76e883ba8a5f = function() { ++__wbindgen_placeholder__.__wbg_new_abda76e883ba8a5f = function() { + const ret = new Error(); + return ret; + }; + +-module.exports.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { ++__wbindgen_placeholder__.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { + const ret = arg1.stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; +@@ -4285,7 +4286,7 @@ module.exports.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + +-module.exports.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) { ++__wbindgen_placeholder__.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { +@@ -4297,7 +4298,7 @@ module.exports.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) { + } + }; + +-module.exports.__wbg_instanceof_Window_c4b70662a0d2c5ec = function(arg0) { ++__wbindgen_placeholder__.__wbg_instanceof_Window_c4b70662a0d2c5ec = function(arg0) { + let result; + try { + result = arg0 instanceof Window; +@@ -4308,42 +4309,42 @@ module.exports.__wbg_instanceof_Window_c4b70662a0d2c5ec = function(arg0) { + return ret; + }; + +-module.exports.__wbg_document_e5c1786dea6542e4 = function(arg0) { ++__wbindgen_placeholder__.__wbg_document_e5c1786dea6542e4 = function(arg0) { + const ret = arg0.document; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }; + +-module.exports.__wbg_body_e70ae6abd01ae584 = function(arg0) { ++__wbindgen_placeholder__.__wbg_body_e70ae6abd01ae584 = function(arg0) { + const ret = arg0.body; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }; + +-module.exports.__wbg_createElement_5d4c76f218b78145 = function() { return handleError(function (arg0, arg1, arg2) { ++__wbindgen_placeholder__.__wbg_createElement_5d4c76f218b78145 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.createElement(getStringFromWasm0(arg1, arg2)); + return ret; + }, arguments) }; + +-module.exports.__wbg_width_4c6f0048d64cf86b = function(arg0) { ++__wbindgen_placeholder__.__wbg_width_4c6f0048d64cf86b = function(arg0) { + const ret = arg0.width; + return ret; + }; + +-module.exports.__wbg_height_21f0d3fd8f753394 = function(arg0) { ++__wbindgen_placeholder__.__wbg_height_21f0d3fd8f753394 = function(arg0) { + const ret = arg0.height; + return ret; + }; + +-module.exports.__wbg_width_79e0847ed5883b03 = function(arg0) { ++__wbindgen_placeholder__.__wbg_width_79e0847ed5883b03 = function(arg0) { + const ret = arg0.width; + return ret; + }; + +-module.exports.__wbg_height_e4e4e4779f8feac0 = function(arg0) { ++__wbindgen_placeholder__.__wbg_height_e4e4e4779f8feac0 = function(arg0) { + const ret = arg0.height; + return ret; + }; + +-module.exports.__wbg_data_fda507064d127f5b = function(arg0, arg1) { ++__wbindgen_placeholder__.__wbg_data_fda507064d127f5b = function(arg0, arg1) { + const ret = arg1.data; + const ptr1 = passArray8ToWasm0(ret, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; +@@ -4351,12 +4352,12 @@ module.exports.__wbg_data_fda507064d127f5b = function(arg0, arg1) { + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + +-module.exports.__wbg_newwithu8clampedarrayandsh_1fddccb3a94a5e05 = function() { return handleError(function (arg0, arg1, arg2, arg3) { ++__wbindgen_placeholder__.__wbg_newwithu8clampedarrayandsh_1fddccb3a94a5e05 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = new ImageData(getClampedArrayU8FromWasm0(arg0, arg1), arg2 >>> 0, arg3 >>> 0); + return ret; + }, arguments) }; + +-module.exports.__wbg_instanceof_CanvasRenderingContext2d_3abbe7ec7af32cae = function(arg0) { ++__wbindgen_placeholder__.__wbg_instanceof_CanvasRenderingContext2d_3abbe7ec7af32cae = function(arg0) { + let result; + try { + result = arg0 instanceof CanvasRenderingContext2D; +@@ -4367,24 +4368,24 @@ module.exports.__wbg_instanceof_CanvasRenderingContext2d_3abbe7ec7af32cae = fun + return ret; + }; + +-module.exports.__wbg_drawImage_fede06db74e39a60 = function() { return handleError(function (arg0, arg1, arg2, arg3) { ++__wbindgen_placeholder__.__wbg_drawImage_fede06db74e39a60 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + arg0.drawImage(arg1, arg2, arg3); + }, arguments) }; + +-module.exports.__wbg_drawImage_f395c8e43c79a909 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { ++__wbindgen_placeholder__.__wbg_drawImage_f395c8e43c79a909 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.drawImage(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); + }, arguments) }; + +-module.exports.__wbg_getImageData_5e1c242046e6b59e = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { ++__wbindgen_placeholder__.__wbg_getImageData_5e1c242046e6b59e = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + const ret = arg0.getImageData(arg1, arg2, arg3, arg4); + return ret; + }, arguments) }; + +-module.exports.__wbg_putImageData_a8b3e177ee06d521 = function() { return handleError(function (arg0, arg1, arg2, arg3) { ++__wbindgen_placeholder__.__wbg_putImageData_a8b3e177ee06d521 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + arg0.putImageData(arg1, arg2, arg3); + }, arguments) }; + +-module.exports.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(arg0) { ++__wbindgen_placeholder__.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(arg0) { + let result; + try { + result = arg0 instanceof HTMLCanvasElement; +@@ -4395,93 +4396,93 @@ module.exports.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(a + return ret; + }; + +-module.exports.__wbg_width_dc225e55343b745e = function(arg0) { ++__wbindgen_placeholder__.__wbg_width_dc225e55343b745e = function(arg0) { + const ret = arg0.width; + return ret; + }; + +-module.exports.__wbg_setwidth_488780db69b08846 = function(arg0, arg1) { ++__wbindgen_placeholder__.__wbg_setwidth_488780db69b08846 = function(arg0, arg1) { + arg0.width = arg1 >>> 0; + }; + +-module.exports.__wbg_height_3a8bec2f3fe71b26 = function(arg0) { ++__wbindgen_placeholder__.__wbg_height_3a8bec2f3fe71b26 = function(arg0) { + const ret = arg0.height; + return ret; + }; + +-module.exports.__wbg_setheight_1761808c18403921 = function(arg0, arg1) { ++__wbindgen_placeholder__.__wbg_setheight_1761808c18403921 = function(arg0, arg1) { + arg0.height = arg1 >>> 0; + }; + +-module.exports.__wbg_getContext_fc99dbd3a9a7e318 = function() { return handleError(function (arg0, arg1, arg2) { ++__wbindgen_placeholder__.__wbg_getContext_fc99dbd3a9a7e318 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.getContext(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments) }; + +-module.exports.__wbg_settextContent_f82a86a8df347e1c = function(arg0, arg1, arg2) { ++__wbindgen_placeholder__.__wbg_settextContent_f82a86a8df347e1c = function(arg0, arg1, arg2) { + arg0.textContent = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2); + }; + +-module.exports.__wbg_appendChild_fa3b00dade9fc4cf = function() { return handleError(function (arg0, arg1) { ++__wbindgen_placeholder__.__wbg_appendChild_fa3b00dade9fc4cf = function() { return handleError(function (arg0, arg1) { + const ret = arg0.appendChild(arg1); + return ret; + }, arguments) }; + +-module.exports.__wbg_newnoargs_e643855c6572a4a8 = function(arg0, arg1) { ++__wbindgen_placeholder__.__wbg_newnoargs_e643855c6572a4a8 = function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return ret; + }; + +-module.exports.__wbg_call_f96b398515635514 = function() { return handleError(function (arg0, arg1) { ++__wbindgen_placeholder__.__wbg_call_f96b398515635514 = function() { return handleError(function (arg0, arg1) { + const ret = arg0.call(arg1); + return ret; + }, arguments) }; + +-module.exports.__wbg_self_b9aad7f1c618bfaf = function() { return handleError(function () { ++__wbindgen_placeholder__.__wbg_self_b9aad7f1c618bfaf = function() { return handleError(function () { + const ret = self.self; + return ret; + }, arguments) }; + +-module.exports.__wbg_window_55e469842c98b086 = function() { return handleError(function () { ++__wbindgen_placeholder__.__wbg_window_55e469842c98b086 = function() { return handleError(function () { + const ret = window.window; + return ret; + }, arguments) }; + +-module.exports.__wbg_globalThis_d0957e302752547e = function() { return handleError(function () { ++__wbindgen_placeholder__.__wbg_globalThis_d0957e302752547e = function() { return handleError(function () { + const ret = globalThis.globalThis; + return ret; + }, arguments) }; + +-module.exports.__wbg_global_ae2f87312b8987fb = function() { return handleError(function () { ++__wbindgen_placeholder__.__wbg_global_ae2f87312b8987fb = function() { return handleError(function () { + const ret = global.global; + return ret; + }, arguments) }; + +-module.exports.__wbindgen_is_undefined = function(arg0) { ++__wbindgen_placeholder__.__wbindgen_is_undefined = function(arg0) { + const ret = arg0 === undefined; + return ret; + }; + +-module.exports.__wbg_buffer_fcbfb6d88b2732e9 = function(arg0) { ++__wbindgen_placeholder__.__wbg_buffer_fcbfb6d88b2732e9 = function(arg0) { + const ret = arg0.buffer; + return ret; + }; + +-module.exports.__wbg_new_bc5d9aad3f9ac80e = function(arg0) { ++__wbindgen_placeholder__.__wbg_new_bc5d9aad3f9ac80e = function(arg0) { + const ret = new Uint8Array(arg0); + return ret; + }; + +-module.exports.__wbg_set_4b3aa8445ac1e91c = function(arg0, arg1, arg2) { ++__wbindgen_placeholder__.__wbg_set_4b3aa8445ac1e91c = function(arg0, arg1, arg2) { + arg0.set(arg1, arg2 >>> 0); + }; + +-module.exports.__wbg_length_d9c4ded7e708c6a1 = function(arg0) { ++__wbindgen_placeholder__.__wbg_length_d9c4ded7e708c6a1 = function(arg0) { + const ret = arg0.length; + return ret; + }; + +-module.exports.__wbindgen_debug_string = function(arg0, arg1) { ++__wbindgen_placeholder__.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; +@@ -4489,16 +4490,16 @@ module.exports.__wbindgen_debug_string = function(arg0, arg1) { + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + +-module.exports.__wbindgen_throw = function(arg0, arg1) { ++__wbindgen_placeholder__.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + +-module.exports.__wbindgen_memory = function() { ++__wbindgen_placeholder__.__wbindgen_memory = function() { + const ret = wasm.memory; + return ret; }; +-module.exports.__wbindgen_init_externref_table = function() { ++__wbindgen_placeholder__.__wbindgen_init_externref_table = function() { + const table = wasm.__wbindgen_export_2; + const offset = table.grow(4); + table.set(0, undefined); +@@ -4509,7 +4510,8 @@ module.exports.__wbindgen_init_externref_table = function() { + ; + }; + -const path = require('path').join(__dirname, 'photon_rs_bg.wasm'); +// Allow opencode's Bun compiled binary to point photon-node at its embedded wasm asset. +const path = globalThis.__OPENCODE_PHOTON_WASM_PATH || require('path').join(__dirname, 'photon_rs_bg.wasm'); From bfd707abc9d846c2be6eb8a0105e604e74a72129 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 14 May 2026 04:07:30 +0000 Subject: [PATCH 326/378] chore: generate --- packages/opencode/src/session/processor.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 7ba9631e6..caf5a2478 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -407,15 +407,13 @@ export const layer: Layer.Layer< ) const normalized = yield* Effect.forEach(toolAttachments, (attachment) => attachment.mime.startsWith("image/") - ? image - .normalize(attachment) - .pipe( - Effect.catchIf( - (error) => error instanceof Image.ResizerUnavailableError, - () => Effect.succeed(attachment), - ), - Effect.exit, - ) + ? image.normalize(attachment).pipe( + Effect.catchIf( + (error) => error instanceof Image.ResizerUnavailableError, + () => Effect.succeed(attachment), + ), + Effect.exit, + ) : Effect.succeed(Exit.succeed(attachment)), ) const omitted = normalized.filter(Exit.isFailure).length From 2a7af6acd889aee0092291bc679b5fcbb4a2c527 Mon Sep 17 00:00:00 2001 From: Nikhil Patel Date: Thu, 14 May 2026 00:17:59 -0400 Subject: [PATCH 327/378] fix(tui): preserve text selection on question prompt options (#24988) Co-authored-by: Aiden Cline --- .../cli/cmd/tui/routes/session/question.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx index e690f6f32..46fc220bd 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx @@ -1,5 +1,6 @@ import { createStore } from "solid-js/store" import { createMemo, createSignal, For, Show } from "solid-js" +import { useRenderer } from "@opentui/solid" import type { TextareaRenderable } from "@opentui/core" import { selectedForeground, tint, useTheme } from "../../context/theme" import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2" @@ -12,6 +13,7 @@ import { useBindings } from "../../keymap" export function QuestionPrompt(props: { request: QuestionRequest }) { const sdk = useSDK() const { theme } = useTheme() + const renderer = useRenderer() const tuiConfig = useTuiConfig() const questions = createMemo(() => props.request.questions) @@ -302,7 +304,10 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { } onMouseOver={() => setTabHover(index())} onMouseOut={() => setTabHover(null)} - onMouseUp={() => selectTab(index())} + onMouseUp={() => { + if (renderer.getSelection()?.getSelectedText()) return + selectTab(index()) + }} > setTabHover("confirm")} onMouseOut={() => setTabHover(null)} - onMouseUp={() => selectTab(questions().length)} + onMouseUp={() => { + if (renderer.getSelection()?.getSelectedText()) return + selectTab(questions().length) + }} > Confirm @@ -351,7 +359,10 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { moveTo(i())} onMouseDown={() => moveTo(i())} - onMouseUp={() => selectOption()} + onMouseUp={() => { + if (renderer.getSelection()?.getSelectedText()) return + selectOption() + }} > @@ -380,7 +391,10 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { moveTo(options().length)} onMouseDown={() => moveTo(options().length)} - onMouseUp={() => selectOption()} + onMouseUp={() => { + if (renderer.getSelection()?.getSelectedText()) return + selectOption() + }} > From 4d8368970abe73c7e2826e4fd67bc7a71d484e24 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 14 May 2026 04:20:42 +0000 Subject: [PATCH 328/378] 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 73a76a1d5..c84fc5210 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-QIj9PhOXR/GngV/dPjCF7n5rKro2fcTNGzJ47a41Z2Q=", - "aarch64-linux": "sha256-fQl7BjjTYtRKT3HRVhubaIVww/puUFSTzVV5bTy8II8=", - "aarch64-darwin": "sha256-81IAmdjiYZz8IgMJt0+VxzdOS80gTHc5SendwEW/vD4=", - "x86_64-darwin": "sha256-5OMX4VVBMfEmkYvzd09oksAt5hKkxDs84miO804LBI8=" + "x86_64-linux": "sha256-XS0A91P0xt3WyqeBSn6cqdZSMgcFURth05g5AMEJWew=", + "aarch64-linux": "sha256-rYsDJagY7pJaHySiwxu/FKNxwY4L0u7j0BoFj3RG7y4=", + "aarch64-darwin": "sha256-NPh4cuCDcfQ+E+7ojLET9ONbUKbw0quq1q9a3kR7gYk=", + "x86_64-darwin": "sha256-pDPuClW66RmoAQeEmQpIdp0wFg0659adO5Saogem1Pw=" } } From 9675579796228d52b43c7b5e05324a79bcad78cc Mon Sep 17 00:00:00 2001 From: Frederik <141146820+frederiknsgo@users.noreply.github.com> Date: Thu, 14 May 2026 06:24:16 +0200 Subject: [PATCH 329/378] fix: bug encountered when using azure gpt-5.5 w/ completions api (#26222) --- packages/opencode/src/provider/transform.ts | 5 ++ .../opencode/test/provider/transform.test.ts | 69 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index d7b0f0e08..8ca71b8ef 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1126,6 +1126,11 @@ export function options(input: { result["enable_thinking"] = true } + if (input.model.api.npm === "@ai-sdk/azure" && input.model.api.id.includes("gpt-5.5")) { + result["reasoningSummary"] = "auto" + return result; + } + if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) { if (!input.model.api.id.includes("gpt-5-pro")) { result["reasoningEffort"] = "medium" diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 9b6615ee8..76ad5bc44 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -310,6 +310,75 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => { }) }) +describe("ProviderTransform.options - gpt-5 reasoningEffort", () => { + const sessionID = "test-session-123" + + const createModel = (apiId: string) => + ({ + id: `azure/${apiId}`, + providerID: "azure", + api: { + id: apiId, + url: "https://azure.com", + npm: "@ai-sdk/azure", + }, + name: apiId, + capabilities: { + temperature: true, + reasoning: true, + attachment: true, + toolcall: true, + input: { + text: true, + audio: false, + image: true, + video: false, + pdf: false, + }, + output: { + text: true, + audio: false, + image: false, + video: false, + pdf: false, + }, + interleaved: false, + }, + cost: { + input: 0.03, + output: 0.06, + cache: { read: 0.001, write: 0.002 }, + }, + limit: { + context: 128000, + output: 4096, + }, + status: "active", + options: {}, + headers: {}, + }) as any + + test('gpt-5-chat should NOT set reasoningEffort', () => { + const result = ProviderTransform.options({ + model: createModel("gpt-5-chat"), + sessionID, + providerOptions: {}, + }) + + expect(result.reasoningEffort).toBeUndefined() + }) + + test("gpt-5.5 should NOT set reasoningEffort", () => { + const result = ProviderTransform.options({ + model: createModel("gpt-5.5"), + sessionID, + providerOptions: {}, + }) + + expect(result.reasoningEffort).toBeUndefined() + }) +}) + describe("ProviderTransform.options - gateway", () => { const sessionID = "test-session-123" From c2723b5ea070ed7e45cc241ac64c0760d427fc56 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 14 May 2026 04:25:32 +0000 Subject: [PATCH 330/378] chore: generate --- packages/opencode/src/provider/transform.ts | 2 +- packages/opencode/test/provider/transform.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 8ca71b8ef..d7be64ca7 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1128,7 +1128,7 @@ export function options(input: { if (input.model.api.npm === "@ai-sdk/azure" && input.model.api.id.includes("gpt-5.5")) { result["reasoningSummary"] = "auto" - return result; + return result } if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) { diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 76ad5bc44..90e2a177f 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -358,7 +358,7 @@ describe("ProviderTransform.options - gpt-5 reasoningEffort", () => { headers: {}, }) as any - test('gpt-5-chat should NOT set reasoningEffort', () => { + test("gpt-5-chat should NOT set reasoningEffort", () => { const result = ProviderTransform.options({ model: createModel("gpt-5-chat"), sessionID, From e76cf967e60995986a4dd99d818fc900fa82f904 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 14 May 2026 01:19:11 -0500 Subject: [PATCH 331/378] fix(session): finalize interrupted assistant messages (#27254) --- packages/opencode/src/session/prompt.ts | 26 +++- packages/opencode/test/session/prompt.test.ts | 125 +++++++++++++++++- 2 files changed, 139 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 6248ce455..5d79bff04 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1755,12 +1755,25 @@ NOTE: At any point in time through this workflow you should feel free to ask the sessionID, } yield* sessions.updateMessage(msg) - const handle = yield* processor.create({ - assistantMessage: msg, - sessionID, - model, + + const finalizeInterruptedAssistant = Effect.gen(function* () { + if (msg.time.completed) return + msg.error ??= MessageV2.fromError(new DOMException("Aborted", "AbortError"), { + providerID: msg.providerID, + aborted: true, + }) + msg.time.completed = Date.now() + yield* sessions.updateMessage(msg) }) + const handle = yield* processor + .create({ + assistantMessage: msg, + sessionID, + model, + }) + .pipe(Effect.onInterrupt(() => finalizeInterruptedAssistant)) + const outcome: "break" | "continue" = yield* Effect.gen(function* () { const lastUserMsg = msgs.findLast((m) => m.info.role === "user") const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false @@ -1859,7 +1872,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the }) } return "continue" as const - }).pipe(Effect.ensuring(instruction.clear(handle.message.id))) + }).pipe( + Effect.ensuring(instruction.clear(handle.message.id)), + Effect.onInterrupt(() => finalizeInterruptedAssistant), + ) if (outcome === "break") break continue } diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 9ad2fbe1f..9ac038aa6 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -152,7 +152,16 @@ const lsp = Layer.succeed( const status = SessionStatus.layer.pipe(Layer.provideMerge(Bus.layer)) const run = SessionRunState.layer.pipe(Layer.provide(status)) const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer) -function makeHttp() { + +const processorCreateStarted: Array<() => void> = [] +const blockingProcessor = Layer.succeed( + SessionProcessor.Service, + SessionProcessor.Service.of({ + create: () => Effect.sync(() => processorCreateStarted.shift()?.()).pipe(Effect.andThen(Effect.never)), + }), +) + +function makeHttp(input?: { processor?: "blocking" }) { const deps = Layer.mergeAll( Session.defaultLayer, Snapshot.defaultLayer, @@ -186,12 +195,15 @@ function makeHttp() { Layer.provideMerge(deps), ) const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) - const proc = SessionProcessor.layer.pipe( - Layer.provide(summary), - Layer.provide(Image.defaultLayer), - Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), - Layer.provideMerge(deps), - ) + const proc = + input?.processor === "blocking" + ? blockingProcessor + : SessionProcessor.layer.pipe( + Layer.provide(summary), + Layer.provide(Image.defaultLayer), + Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), + Layer.provideMerge(deps), + ) const compact = SessionCompaction.layer.pipe( Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })), Layer.provideMerge(proc), @@ -218,6 +230,7 @@ function makeHttp() { } const it = testEffect(makeHttp()) +const race = testEffect(makeHttp({ processor: "blocking" })) const unix = process.platform !== "win32" ? it.instance : it.instance.skip // Config that registers a custom "test" provider with a "test-model" model @@ -341,6 +354,14 @@ const deferredAsPromise = (deferred: Deferred.Deferred): PromiseLike => }, }) +function defer() { + let resolve!: (value: T | PromiseLike) => void + const promise = new Promise((done) => { + resolve = done + }) + return { promise, resolve } +} + const succeedVoid = (deferred: Deferred.Deferred) => { Effect.runSync(Deferred.succeed(deferred, void 0).pipe(Effect.ignore)) } @@ -896,6 +917,96 @@ it.instance( 3_000, ) +race.instance( + "finalizes assistant when cancelled before processor creation completes", + () => + Effect.gen(function* () { + yield* useServerConfig(providerCfg) + processorCreateStarted.length = 0 + yield* Effect.addFinalizer(() => + Effect.sync(() => { + processorCreateStarted.length = 0 + }), + ) + + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Processor creation race" }) + + yield* prompt.prompt({ + sessionID: chat.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "first" }], + }) + + const firstCreate = defer() + processorCreateStarted.push(firstCreate.resolve) + const first = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* Effect.promise(() => firstCreate.promise) + + yield* prompt.cancel(chat.id) + const firstExit = yield* Fiber.await(first) + expect(Exit.isSuccess(firstExit)).toBe(true) + + let messages = yield* sessions.messages({ sessionID: chat.id }) + const firstInterrupted = messages.at(-1) + expect(firstInterrupted?.info.role).toBe("assistant") + expect(firstInterrupted?.parts).toHaveLength(0) + if (firstInterrupted?.info.role === "assistant") { + expect(firstInterrupted.info.finish).toBeUndefined() + expect(firstInterrupted.info.time.completed).toBeNumber() + expect(firstInterrupted.info.error?.name).toBe("MessageAbortedError") + } + + yield* prompt.prompt({ + sessionID: chat.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "second" }], + }) + + const secondCreate = defer() + processorCreateStarted.push(secondCreate.resolve) + const second = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* Effect.promise(() => secondCreate.promise) + + yield* prompt.cancel(chat.id) + const secondExit = yield* Fiber.await(second) + expect(Exit.isSuccess(secondExit)).toBe(true) + + messages = yield* sessions.messages({ sessionID: chat.id }) + const poisonMessages = messages.filter( + (message) => + message.info.role === "assistant" && + message.parts.length === 0 && + !message.info.finish && + !message.info.time.completed && + !message.info.error, + ) + expect(poisonMessages).toHaveLength(0) + + const interruptedMessages = messages.filter( + (message) => + message.info.role === "assistant" && + message.parts.length === 0 && + message.info.time.completed && + message.info.error?.name === "MessageAbortedError", + ) + expect(interruptedMessages).toHaveLength(2) + + const lastUser = messages.at(-2) + const lastAssistant = messages.at(-1) + expect(lastUser?.info.role).toBe("user") + expect(lastAssistant?.info.role).toBe("assistant") + if (lastUser?.info.role === "user" && lastAssistant?.info.role === "assistant") { + expect(lastAssistant.info.parentID).toBe(lastUser?.info.id) + } + }), + { git: true }, + 3_000, +) + it.instance( "cancel finalizes subtask tool state", () => From 78015571bf95544d68403ce085c03c9829f33ceb Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 14 May 2026 12:50:36 +0530 Subject: [PATCH 332/378] refactor(server): centralize session busy mapping (#27473) --- .../instance/httpapi/handlers/session-errors.ts | 6 ++++++ .../routes/instance/httpapi/handlers/session.ts | 13 ++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts index 0fef2e776..d4ab0eb59 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session-errors.ts @@ -1,7 +1,13 @@ import type { NotFoundError as StorageNotFoundError } from "@/storage/storage" +import type { Session } from "@/session/session" import { Effect } from "effect" +import { HttpApiError } from "effect/unstable/httpapi" import * as ApiError from "../errors" export function mapStorageNotFound(self: Effect.Effect) { return self.pipe(Effect.mapError((error) => ApiError.notFound(error.message))) } + +export function mapBusy(self: Effect.Effect) { + return self.pipe(Effect.catchTag("SessionBusyError", () => Effect.fail(new HttpApiError.BadRequest({})))) +} diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index e3f79965d..6509aa2b1 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -58,11 +58,6 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", const bus = yield* Bus.Service const scope = yield* Scope.Scope - const mapBusy = ( - effect: Effect.Effect, - ): Effect.Effect => - effect.pipe(Effect.catchTag("SessionBusyError", () => Effect.fail(new HttpApiError.BadRequest({})))) - const list = Effect.fn("SessionHttpApi.list")(function* (ctx: { query: typeof ListQuery.Type }) { return yield* session.list({ directory: ctx.query.scope === "project" ? undefined : ctx.query.directory, @@ -334,7 +329,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", payload: typeof ShellPayload.Type }) { yield* requireSession(ctx.params.sessionID) - return yield* mapBusy(promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID })) + return yield* SessionError.mapBusy(promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID })) }) const revert = Effect.fn("SessionHttpApi.revert")(function* (ctx: { @@ -342,12 +337,12 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", payload: typeof RevertPayload.Type }) { yield* requireSession(ctx.params.sessionID) - return yield* mapBusy(revertSvc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload })) + return yield* SessionError.mapBusy(revertSvc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload })) }) const unrevert = Effect.fn("SessionHttpApi.unrevert")(function* (ctx: { params: { sessionID: SessionID } }) { yield* requireSession(ctx.params.sessionID) - return yield* mapBusy(revertSvc.unrevert({ sessionID: ctx.params.sessionID })) + return yield* SessionError.mapBusy(revertSvc.unrevert({ sessionID: ctx.params.sessionID })) }) const permissionRespond = Effect.fn("SessionHttpApi.permissionRespond")(function* (ctx: { @@ -363,7 +358,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", params: { sessionID: SessionID; messageID: MessageID } }) { yield* requireSession(ctx.params.sessionID) - yield* mapBusy(runState.assertNotBusy(ctx.params.sessionID)) + yield* SessionError.mapBusy(runState.assertNotBusy(ctx.params.sessionID)) yield* session.removeMessage(ctx.params) return true }) From 27ac53aaacc677b1401c4e75ca7a7dadf8b2c349 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 14 May 2026 12:51:05 +0530 Subject: [PATCH 333/378] fix(server): stop exposing named defects (#27471) --- .../instance/httpapi/middleware/error.ts | 3 --- .../server/httpapi-error-middleware.test.ts | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts index d4b6dbbab..74c690ad6 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts @@ -20,9 +20,6 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) const error = defect.defect log.error("failed", { error, cause: Cause.pretty(cause) }) - if (error instanceof NamedError) { - return Effect.succeed(HttpServerResponse.jsonUnsafe(error.toObject(), { status: 500 })) - } return Effect.succeed( HttpServerResponse.jsonUnsafe( new NamedError.Unknown({ diff --git a/packages/opencode/test/server/httpapi-error-middleware.test.ts b/packages/opencode/test/server/httpapi-error-middleware.test.ts index f53a9e887..15f8aa202 100644 --- a/packages/opencode/test/server/httpapi-error-middleware.test.ts +++ b/packages/opencode/test/server/httpapi-error-middleware.test.ts @@ -1,4 +1,5 @@ import { NodeHttpServer, NodeServices } from "@effect/platform-node" +import { NamedError } from "@opencode-ai/core/util/error" import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http" @@ -29,6 +30,26 @@ describe("HttpApi error middleware", () => { }), ) + it.live("returns a safe body for named defects", () => + Effect.gen(function* () { + yield* HttpRouter.add( + "GET", + "/named", + Effect.die(new NamedError.Unknown({ message: "secret named marker" })), + ).pipe(Layer.provide(errorLayer), HttpRouter.serve, Layer.build) + + const response = yield* HttpClientRequest.get("/named").pipe(HttpClient.execute) + const body = yield* response.json + + expect(response.status).toBe(500) + expect(body).toEqual({ + name: "UnknownError", + data: { message: "Unexpected server error. Check server logs for details." }, + }) + expect(JSON.stringify(body)).not.toContain("secret named marker") + }), + ) + it.live("does not map storage not-found defects to 404", () => Effect.gen(function* () { yield* HttpRouter.add( From 0af242974c7fc1adb55b99667cd32332d6a22bcd Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Thu, 14 May 2026 11:14:03 +0200 Subject: [PATCH 334/378] deps: Upgrade OpenTUI to 0.2.10 (#27491) --- bun.lock | 30 +++++++++++++++--------------- package.json | 6 +++--- packages/plugin/package.json | 6 +++--- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/bun.lock b/bun.lock index 51fdda48e..a22033aa9 100644 --- a/bun.lock +++ b/bun.lock @@ -536,9 +536,9 @@ "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.2.9", - "@opentui/keymap": ">=0.2.9", - "@opentui/solid": ">=0.2.9", + "@opentui/core": ">=0.2.10", + "@opentui/keymap": ">=0.2.10", + "@opentui/solid": ">=0.2.10", }, "optionalPeers": [ "@opentui/core", @@ -721,9 +721,9 @@ "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@opentui/core": "0.2.9", - "@opentui/keymap": "0.2.9", - "@opentui/solid": "0.2.9", + "@opentui/core": "0.2.10", + "@opentui/keymap": "0.2.10", + "@opentui/solid": "0.2.10", "@pierre/diffs": "1.1.0-beta.18", "@playwright/test": "1.59.1", "@sentry/solid": "10.36.0", @@ -1590,23 +1590,23 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.2.9", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.9", "@opentui/core-darwin-x64": "0.2.9", "@opentui/core-linux-arm64": "0.2.9", "@opentui/core-linux-x64": "0.2.9", "@opentui/core-win32-arm64": "0.2.9", "@opentui/core-win32-x64": "0.2.9" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-Kmeqi+yiDau+P45xDeX08GS50FK917qVwuPTN7HGxsQ9Byt7Iifq/6OMiSnFULBzoZtECdKLgQF1XwLsNm1wig=="], + "@opentui/core": ["@opentui/core@0.2.10", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.10", "@opentui/core-darwin-x64": "0.2.10", "@opentui/core-linux-arm64": "0.2.10", "@opentui/core-linux-x64": "0.2.10", "@opentui/core-win32-arm64": "0.2.10", "@opentui/core-win32-x64": "0.2.10" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-oviCtx0jYjc7F8X2b8+0IkQLg6WH47Nwl6CFeZo5dU0k6OpSbTbi07ZleObaiECAp+S1YLhAtVdgzHU7hBZlaw=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-D2ne8Xgyrg71L/9lF7vPh30Sxz6+3yAqpT0m87WiI+040J7sQEyK3YM/7w5JKuVemQ4H54HSPjofrUHjfibjoQ=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+lbDDj42Og+UtTZEwlHhGXichmOlkxSqn0J+Jqjat5/Tt5oZykj1NZjFIQ7ZSz4Miz7EmZwgYKE2CyOmmm9MoQ=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ymbbt/wN/vgB8g+kbHospJclVKHq6cdgfEYg9qgsSHp2vqMFBqlQQ692MS3BcZfX9jrKROK7NvC6Hj37X5K/7Q=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-5iAoA0aqMWWAQ93nh8Bb0ipwt9h+tvEFc88+YO9St43uUJ+XrXcmMj3T8wtl6dSu/SN0UoDWNaUMHUmtykiPtg=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-0RIrVe4+42oELHtSJBaaYhngUeMKwSeqfdtKeSwEFwCzrqrNXxCpXQdOo8QvjOKGgng4Smn6O6KM8sgCj4SSPQ=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-EnrkxgH5K76Oi/Br1UHPZblXG5P60snmtySfnxuVaeECNZrbTkV6BV/A0WoBeWshJweGbx1D+eTF+sEEjQCi8w=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.9", "", { "os": "linux", "cpu": "x64" }, "sha512-fjCZP1IOLWm68FYl2PRzFg1vfu226FPfiJsdNtLbhaYF2uEZOB/v1BQph21OKnB7GC7X8GQatvhM5sS3DQ2MSQ=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.10", "", { "os": "linux", "cpu": "x64" }, "sha512-fI+r3kCPqIxsWwPVGpKUQy4zHK8y+jkDRCwa3UbaUy48RQ44jMuf2RhVhmi4xmCvSc8UPJBbYsw1tLuh9kmXjg=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-u8SP3u2QEJqcGIULYZ7Lkht9ss7wcN4/LnMuqt9rPOiCduFn/VW4r8lQCftZ6DRSqyoP9mJ1xLzOSFl98UYyEw=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-8F4z2hIRgkVWcr6CMVeJ9N4+1rmURPt2Pq2GBPko8ch6rxHR+a//KD1MfphyuLTHBS1tJ4vfZSWSoiaESImtrA=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.9", "", { "os": "win32", "cpu": "x64" }, "sha512-un7iSy9XHLwa6ouVpUj3eEGnXfPG50OMUJ2Dt30Jvn2vhNwIU2VO4RGx06l5OUD6GGVpHb0RqmG/384oo9i+HA=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.10", "", { "os": "win32", "cpu": "x64" }, "sha512-Ki+qNBlIFW5K2wcG/RHrlPp7yEQKXeiNX3mlje25iwX62Ac5w391HBpOmUjbPoq20McPyDRnhbLfbXQSPtickg=="], - "@opentui/keymap": ["@opentui/keymap@0.2.9", "", { "dependencies": { "@opentui/core": "0.2.9" }, "peerDependencies": { "@opentui/react": "0.2.9", "@opentui/solid": "0.2.9", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-yCc6L0Jqa8aVaNAVniTV5bNygJayUE6mxWfaBQY5VV5QwsZemXSeQQc4vP2eetH4Rrm1gGA59gLP+zh6+s5fvw=="], + "@opentui/keymap": ["@opentui/keymap@0.2.10", "", { "dependencies": { "@opentui/core": "0.2.10" }, "peerDependencies": { "@opentui/react": "0.2.10", "@opentui/solid": "0.2.10", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-80fU3Lr/98sNIpVYd8PApAeQw8A8D9BemyOGi6jGvTQCl0rxKgvaVBviDRGKxl1INTVjZy9By8UPncc2KJOuWQ=="], - "@opentui/solid": ["@opentui/solid@0.2.9", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.9", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-qpNSCELxRvBAx8Zneqz46FYYTvJNFjDvhqzAAZRNoaHathfU6X6iPxWMUqP/9ls5VcHFW1TDJdgtpsq1N/nHMQ=="], + "@opentui/solid": ["@opentui/solid@0.2.10", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.10", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-+4/MB90yIQiPwg8Y4wY092yva9BvRTsJeeeEO3e2H7P8k8zxYk4G9bzuhqYLxA9mTVQ+zVDlrmFoPQhT7vpIRw=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/package.json b/package.json index 50406e1f4..a3400fbfb 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ "@types/cross-spawn": "6.0.6", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", - "@opentui/core": "0.2.9", - "@opentui/keymap": "0.2.9", - "@opentui/solid": "0.2.9", + "@opentui/core": "0.2.10", + "@opentui/keymap": "0.2.10", + "@opentui/solid": "0.2.10", "ulid": "3.0.1", "@kobalte/core": "0.13.11", "@types/luxon": "3.7.1", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index fb794c026..2c558bf78 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -22,9 +22,9 @@ "zod": "catalog:" }, "peerDependencies": { - "@opentui/core": ">=0.2.9", - "@opentui/keymap": ">=0.2.9", - "@opentui/solid": ">=0.2.9" + "@opentui/core": ">=0.2.10", + "@opentui/keymap": ">=0.2.10", + "@opentui/solid": ">=0.2.10" }, "peerDependenciesMeta": { "@opentui/core": { From be6e7b309eee2f53a3913db9ef4ba43132b9287d Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 14 May 2026 14:48:58 +0530 Subject: [PATCH 335/378] refactor(provider): type init errors (#27484) --- packages/opencode/src/cli/error.ts | 5 +++-- packages/opencode/src/provider/provider.ts | 18 +++++++++++------- packages/opencode/test/cli/error.test.ts | 8 ++++++++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index ffb0e0cfb..c92369b0a 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -72,8 +72,9 @@ export function FormatError(input: unknown) { } // ProviderInitError: { providerID: string } - if (NamedError.hasName(input, "ProviderInitError")) { - return `Failed to initialize provider "${(input as ErrorLike).data?.providerID}". Check credentials and configuration.` + const providerInit = configData(input, "ProviderInitError") + if (providerInit) { + return `Failed to initialize provider "${stringField(providerInit, "providerID")}". Check credentials and configuration.` } // ConfigJsonError: { path: string, message?: string } diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 09dc40bc4..6401518c7 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -13,7 +13,6 @@ import { Auth } from "../auth" import { Env } from "../env" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { Flag } from "@opencode-ai/core/flag/flag" -import { NamedError } from "@opencode-ai/core/util/error" import { iife } from "@/util/iife" import { Global } from "@opencode-ai/core/global" import path from "path" @@ -975,7 +974,16 @@ export class ModelNotFoundError extends Schema.TaggedErrorClass()("ProviderInitError", { + providerID: ProviderID, + cause: Schema.optional(Schema.Defect), +}) { + static isInstance(input: unknown): input is InitError { + return input instanceof InitError + } +} + +export type Error = ModelNotFoundError | InitError export interface Interface { readonly list: () => Effect.Effect> @@ -1634,7 +1642,7 @@ const layer = Layer.effect( s.sdk.set(key, loaded) return loaded as SDK } catch (e) { - throw new InitError({ providerID: model.providerID }, { cause: e }) + throw new InitError({ providerID: model.providerID, cause: e }) } } @@ -1827,8 +1835,4 @@ export function parseModel(model: string) { } } -export const InitError = NamedError.create("ProviderInitError", { - providerID: ProviderID, -}) - export * as Provider from "./provider" diff --git a/packages/opencode/test/cli/error.test.ts b/packages/opencode/test/cli/error.test.ts index 63e04695d..b29ca2b3b 100644 --- a/packages/opencode/test/cli/error.test.ts +++ b/packages/opencode/test/cli/error.test.ts @@ -81,6 +81,14 @@ describe("cli.error", () => { expect(FormatError({ _tag: "ProviderModelNotFoundError", ...data })).toBe(expected) }) + test("formats legacy and tagged provider init errors the same way", () => { + const data = { providerID: "anthropic" } + const expected = 'Failed to initialize provider "anthropic". Check credentials and configuration.' + + expect(FormatError({ name: "ProviderInitError", data })).toBe(expected) + expect(FormatError({ _tag: "ProviderInitError", ...data })).toBe(expected) + }) + test("formats cancelled UI errors as empty output", () => { expect(FormatError(new UI.CancelledError())).toBe("") }) From 52db7a76e2dbb2a8a1478d98b0c0a4ec7c4a02c9 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 14 May 2026 09:28:30 +0000 Subject: [PATCH 336/378] 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 c84fc5210..0e3f9c49a 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-XS0A91P0xt3WyqeBSn6cqdZSMgcFURth05g5AMEJWew=", - "aarch64-linux": "sha256-rYsDJagY7pJaHySiwxu/FKNxwY4L0u7j0BoFj3RG7y4=", - "aarch64-darwin": "sha256-NPh4cuCDcfQ+E+7ojLET9ONbUKbw0quq1q9a3kR7gYk=", - "x86_64-darwin": "sha256-pDPuClW66RmoAQeEmQpIdp0wFg0659adO5Saogem1Pw=" + "x86_64-linux": "sha256-Hw7sVV9rTm6qBMtdwfLIV2QvxvLQY5qrywXzuyYbhcs=", + "aarch64-linux": "sha256-++oXnY7YqrYt0Qv7ZISmoHliARM9qEP8FacqLxGZH1c=", + "aarch64-darwin": "sha256-kZVa0R1YbuvtTzpETqK6ddj4ISje5jBFHBdlynkhW7Q=", + "x86_64-darwin": "sha256-94eagNDa8GGJxF8BsMX2BF5Pa+QTl48lXL1+6HgEn0I=" } } From 7e43d3e3f5fe8dc278d3b539488727d81d9b7181 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 14 May 2026 15:20:02 +0530 Subject: [PATCH 337/378] refactor(lsp): type initialize errors (#27494) --- packages/opencode/src/lsp/client.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index ac9706fc3..30577a8f1 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -9,7 +9,6 @@ import { Process } from "@/util/process" import { LANGUAGE_EXTENSIONS } from "./language" import { Schema } from "effect" import type * as LSPServer from "./server" -import { NamedError } from "@opencode-ai/core/util/error" import { withTimeout } from "../util/timeout" import { Filesystem } from "@/util/filesystem" @@ -31,9 +30,10 @@ export type Info = NonNullable>> export type Diagnostic = VSCodeDiagnostic -export const InitializeError = NamedError.create("LSPInitializeError", { +export class InitializeError extends Schema.TaggedErrorClass()("LSPInitializeError", { serverID: Schema.String, -}) + cause: Schema.optional(Schema.Defect), +}) {} export const Event = { Diagnostics: BusEvent.define( @@ -275,12 +275,7 @@ export async function create(input: { serverID: string; server: LSPServer.Handle INITIALIZE_TIMEOUT_MS, ).catch((err) => { logger.error("initialize error", { error: err }) - throw new InitializeError( - { serverID: input.serverID }, - { - cause: err, - }, - ) + throw new InitializeError({ serverID: input.serverID, cause: err }) }) const syncKind = getSyncKind(initialized.capabilities) From f8c3f560d42d1764adb7159e86e2061fd3adb0b8 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Thu, 14 May 2026 17:52:23 +0800 Subject: [PATCH 338/378] fix(desktop): await execFilePromise and read stdout properly (#27499) --- packages/desktop/src/main/apps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop/src/main/apps.ts b/packages/desktop/src/main/apps.ts index bf25417b8..f9c0a603e 100644 --- a/packages/desktop/src/main/apps.ts +++ b/packages/desktop/src/main/apps.ts @@ -58,7 +58,7 @@ async function checkMacosApp(appName: string) { async function resolveWindowsAppPath(appName: string): Promise { let output: string try { - output = execFilePromise("where", [appName]).toString() + output = await execFilePromise("where", [appName]).then((r) => r.stdout.toString()) } catch { return null } From 8c1ce0b80ce42c193f3a48183c618d45c51743ab Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 14 May 2026 15:56:02 +0530 Subject: [PATCH 339/378] refactor(flags): simplify tui plugin runtime flags (#27506) --- packages/opencode/src/cli/cmd/tui/plugin/runtime.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index 420826ad0..2a9ebc4ed 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -1067,14 +1067,16 @@ async function load(input: { api: Api; config: TuiConfig.Resolved; dispose?: () } runtime = next try { + const flags = await Effect.runPromise( + Effect.gen(function* () { + return yield* RuntimeFlags.Service + }).pipe(Effect.provide(RuntimeFlags.defaultLayer)), + ) const records = Flag.OPENCODE_PURE ? [] : (config.plugin_origins ?? []) if (Flag.OPENCODE_PURE && config.plugin_origins?.length) { log.info("skipping external tui plugins in pure mode", { count: config.plugin_origins.length }) } - const flags = await Effect.runPromise( - RuntimeFlags.Service.use((flags) => Effect.succeed(flags)).pipe(Effect.provide(RuntimeFlags.defaultLayer)), - ) for (const item of internalTuiPlugins(flags)) { log.info("loading internal tui plugin", { id: item.id }) const entry = loadInternalPlugin(item) From e26abd8da9d28d0e79a481a1e0c0d6b0a70f05ee Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 14 May 2026 16:34:42 +0530 Subject: [PATCH 340/378] fix(tool): close shell truncation stream (#27517) --- packages/opencode/src/tool/shell.ts | 37 ++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/opencode/src/tool/shell.ts b/packages/opencode/src/tool/shell.ts index d3ca54268..fcfd59f35 100644 --- a/packages/opencode/src/tool/shell.ts +++ b/packages/opencode/src/tool/shell.ts @@ -443,6 +443,31 @@ export const ShellTool = Tool.define( let expired = false let aborted = false + const closeSink = Effect.fnUntraced(function* () { + const stream = sink + if (!stream) return + sink = undefined + if (stream.destroyed || stream.closed) return + yield* Effect.promise( + () => + new Promise((resolve) => { + let settled = false + const done = () => { + if (settled) return + settled = true + stream.off("close", done) + stream.off("error", done) + stream.off("finish", done) + resolve() + } + stream.once("close", done) + stream.once("error", done) + stream.once("finish", done) + stream.end(done) + }), + ).pipe(Effect.catch(() => Effect.void)) + }) + yield* ctx.metadata({ metadata: { output: "", @@ -452,6 +477,7 @@ export const ShellTool = Tool.define( const code: number | null = yield* Effect.scoped( Effect.gen(function* () { + yield* Effect.addFinalizer(closeSink) const handle = yield* spawner.spawn(cmd(input.shell, input.command, input.cwd, input.env)) yield* Effect.forkScoped( @@ -555,17 +581,6 @@ export const ShellTool = Tool.define( if (meta.length > 0) { output += "\n\n\n" + meta.join("\n") + "\n" } - if (sink) { - const stream = sink - yield* Effect.promise( - () => - new Promise((resolve) => { - stream.end(() => resolve()) - stream.on("error", () => resolve()) - }), - ) - } - return { title: input.description, metadata: { From 337993d53e7de23711d704eaa3d75f84857e8ddc Mon Sep 17 00:00:00 2001 From: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Date: Thu, 14 May 2026 14:38:52 +0200 Subject: [PATCH 341/378] feat(desktop): add mcp client registration status and authentication handling (#27525) --- .../app/src/components/dialog-select-mcp.tsx | 18 ++++++++++------ .../src/components/status-popover-body.tsx | 21 ++++++++++++++++--- .../app/src/components/status-popover.tsx | 14 ++++++++----- packages/app/src/i18n/ar.ts | 1 + packages/app/src/i18n/br.ts | 1 + packages/app/src/i18n/bs.ts | 1 + packages/app/src/i18n/da.ts | 1 + packages/app/src/i18n/de.ts | 1 + packages/app/src/i18n/en.ts | 1 + packages/app/src/i18n/es.ts | 1 + packages/app/src/i18n/fr.ts | 1 + packages/app/src/i18n/ja.ts | 1 + packages/app/src/i18n/ko.ts | 1 + packages/app/src/i18n/no.ts | 1 + packages/app/src/i18n/pl.ts | 1 + packages/app/src/i18n/ru.ts | 1 + packages/app/src/i18n/th.ts | 1 + packages/app/src/i18n/tr.ts | 1 + packages/app/src/i18n/zh.ts | 1 + packages/app/src/i18n/zht.ts | 1 + 20 files changed, 56 insertions(+), 14 deletions(-) diff --git a/packages/app/src/components/dialog-select-mcp.tsx b/packages/app/src/components/dialog-select-mcp.tsx index cc841e278..5a28173ea 100644 --- a/packages/app/src/components/dialog-select-mcp.tsx +++ b/packages/app/src/components/dialog-select-mcp.tsx @@ -13,6 +13,7 @@ const statusLabels = { connected: "mcp.status.connected", failed: "mcp.status.failed", needs_auth: "mcp.status.needs_auth", + needs_client_registration: "mcp.status.needs_client_registration", disabled: "mcp.status.disabled", } as const @@ -31,8 +32,16 @@ export const DialogSelectMcp: Component = () => { const toggle = useMutation(() => ({ mutationFn: async (name: string) => { - if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name }) - else await sdk.client.mcp.connect({ name }) + const status = sync.data.mcp[name] + if (status?.status === "connected") { + await sdk.client.mcp.disconnect({ name }) + return + } + if (status?.status === "needs_auth") { + await sdk.client.mcp.auth.authenticate({ name }) + return + } + await sdk.client.mcp.connect({ name }) }, onSuccess: () => queryClient.refetchQueries(queryOptions.mcp(pathKey(sync.directory))), })) @@ -67,7 +76,7 @@ export const DialogSelectMcp: Component = () => { } const error = () => { const s = mcpStatus() - return s?.status === "failed" ? s.error : undefined + if (s?.status === "failed" || s?.status === "needs_client_registration") return s.error } const enabled = () => status() === "connected" return ( @@ -78,9 +87,6 @@ export const DialogSelectMcp: Component = () => { {statusLabel()} - - {language.t("common.loading.ellipsis")} -

SAouEk$DE2bkzyEx7##1Zc3kHwP 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

{error()} diff --git a/packages/app/src/components/status-popover-body.tsx b/packages/app/src/components/status-popover-body.tsx index 405c7538c..d06244823 100644 --- a/packages/app/src/components/status-popover-body.tsx +++ b/packages/app/src/components/status-popover-body.tsx @@ -145,7 +145,15 @@ const useMcpToggleMutation = () => { return useMutation(() => ({ mutationFn: async (name: string) => { const status = sync.data.mcp[name] - await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name })) + if (status?.status === "connected") { + await sdk.client.mcp.disconnect({ name }) + return + } + if (status?.status === "needs_auth") { + await sdk.client.mcp.auth.authenticate({ name }) + return + } + await sdk.client.mcp.connect({ name }) }, onSuccess: () => queryClient.refetchQueries(queryOptions.mcp(pathKey(sync.directory))), onError: (err) => { @@ -316,7 +324,7 @@ export function StatusPopoverBody(props: { shown: Accessor }) { return (
+
+
+
opencode v2
+

API map

+
+
+

+ A single /api route surface for simple clients and multi-directory frontends. The important + design question is not route nesting; it is where runtime context comes from. +

+
+ Server scoped + Request context + Session pinned +
+
+
+ +
+
+ Everything has one canonical route. Some routes are server-scoped; runtime routes use context; session item routes use the session. +

+ Server-scoped routes manage the whole server: projects, workspace lifecycle, and auth accounts. Runtime + context is for anything resolved from an active directory, including config, provider capabilities, tools, + files, and VCS. +

+
+
+
+ +
+
+

Context Model

+ + API context resolution + Non-session routes resolve from request context, session item routes resolve from session storage. + + + + + + + + Non-session route + /api/file, /api/vcs/status + + + Request context + query params or default runtime + + + Runtime context + directory + workspaceID? + + + Session item route + /api/session/:id/prompt + + + Session row + contains pinned context + + + Runtime context + directory + workspaceID? + +
+ +
+

Request-context calls

+

+ These calls operate against a directory, optionally through a workspace. Simple clients omit context and + use the default runtime. +

+
GET /api/fs/tree?path=.&directory=/repo/app&workspace=ws_123
+
+ +
+

Session-pinned calls

+

+ These calls never take request context. The session is already pinned to the directory and workspace it was + created in. +

+
POST /api/session/ses_123/prompt
+
+// server resolves
+sessionID -> { directory, workspaceID? }
+
+
+ +
+
+

Operation Inventory

+

+ The SDK is the source of truth. HTTP routes are mounts for RPC-style operations. server operations do not use runtime context. request operations use request/default runtime context from directory and workspace query parameters. session operations use pinned session context and should not accept context input. +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OperationInputContextHTTP mountPurpose
agent.list{}requestGET /api/agentAvailable agents.
auth.activate{ accountID: AccountID }serverPOST /api/auth/:accountID/activateSet the account as active for its service.
auth.create{ + serviceID: ServiceID + credential: + | { type: "oauth", refresh: string, access: string, expires: number } + | { type: "api", key: string, metadata?: Record<string, string> } + description?: string + active?: boolean +}serverPOST /api/authCreate an auth account.
auth.delete{ accountID: AccountID }serverDELETE /api/auth/:accountIDRemove an auth account.
auth.get{ accountID: AccountID }serverGET /api/auth/:accountIDGet one auth account.
auth.list{ serviceID?: ServiceID }serverGET /api/authList saved auth accounts. Response includes active account mapping.
auth.update{ + accountID: AccountID + description?: string + credential?: + | { type: "oauth", refresh: string, access: string, expires: number } + | { type: "api", key: string, metadata?: Record<string, string> } +}serverPATCH /api/auth/:accountIDUpdate account description or credential.
catalog.model.get{ + providerID: ProviderID + modelID: ModelID +}serverGET /api/catalog/model/:providerID/:modelIDGet one catalog model.
catalog.model.list{}serverGET /api/catalog/modelList flattened catalog models.
command.list{}requestGET /api/commandAvailable commands.
config.get{}requestGET /api/configResolved config.
config.update{ config: Config }requestPATCH /api/configUpdate config.
event.subscribe{}requestGET /api/eventServer-sent events for the resolved runtime context.
formatter.status{}requestGET /api/formatterFormatter status.
fs.file{ path: string }requestGET /api/fs/fileRead one file.
fs.grep{ + pattern: string + include?: string + limit?: number +}requestPOST /api/fs/grepSearch file contents.
fs.search{ + query: string + type?: "file" | "directory" + limit?: number +}requestPOST /api/fs/searchSearch paths by name.
fs.tree{ path: string }requestGET /api/fs/treeBrowse a directory.
lsp.status{}requestGET /api/lspLSP status.
mcp.prompt.list{}requestGET /api/mcp/promptList MCP prompts.
mcp.prompt.render{ + server: string + name: string + arguments?: Record<string, string> +}requestPOST /api/mcp/prompt/renderRender one MCP prompt.
mcp.resource.list{}requestGET /api/mcp/resourceList MCP resources.
mcp.resource.read{ + server: string + uri: string +}requestGET /api/mcp/resource/readRead one MCP resource.
mcp.server.create{ + name: string + config: + | { type: "local", command: string, arguments?: string[], environment?: Record<string, string> } + | { type: "remote", url: string, headers?: Record<string, string>, oauth?: boolean | object } +}requestPOST /api/mcp/serverAdd an MCP server to runtime config.
mcp.server.list{}requestGET /api/mcp/serverList MCP servers with status and auth state.
mcp.server.oauth.callback{ + name: string + code: string +}requestPOST /api/mcp/server/:name/oauth/callbackComplete MCP OAuth.
mcp.server.oauth.delete{ name: string }requestDELETE /api/mcp/server/:name/oauthRemove MCP OAuth credentials.
mcp.server.oauth.start{ name: string }requestPOST /api/mcp/server/:name/oauthStart MCP OAuth.
permission.list{}requestGET /api/permissionPending permission requests.
permission.reply{ + permissionID: PermissionID + response: PermissionReply +}requestPOST /api/permission/:permissionID/replyReply to a permission request.
project.get{ projectID: ProjectID }serverGET /api/project/:projectIDGet project metadata.
project.list{}serverGET /api/projectList projects known to this server.
project.update{ + projectID: ProjectID + name?: string + icon?: string + commands?: Array<{ + name: string + command: string + }> +}serverPATCH /api/project/:projectIDUpdate project metadata.
provider.list{}requestGET /api/providerProvider inventory for the runtime context.
pty.create{ + command?: string + cwd?: string + shell?: string +}requestPOST /api/ptyCreate PTY in the runtime context.
pty.delete{ ptyID: PtyID }requestDELETE /api/pty/:ptyIDDelete PTY.
pty.get{ ptyID: PtyID }requestGET /api/pty/:ptyIDGet PTY info.
pty.list{}requestGET /api/ptyList PTYs for the runtime.
pty.update{ + ptyID: PtyID + title?: string + size?: { columns: number, rows: number } +}requestPATCH /api/pty/:ptyIDUpdate PTY.
question.list{}requestGET /api/questionPending user questions.
question.reject{ questionID: QuestionID }requestPOST /api/question/:questionID/rejectReject a question.
question.reply{ + questionID: QuestionID + response: QuestionResponse +}requestPOST /api/question/:questionID/replyReply to a question.
session.compact{ sessionID: SessionID }sessionPOST /api/session/:sessionID/compactCompact the session conversation.
session.context{ sessionID: SessionID }sessionGET /api/session/:sessionID/contextReturn active context messages after the last compaction.
session.create{ + title?: string + agent?: string + model?: { providerID: ProviderID, modelID: ModelID } + permission?: PermissionRule[] +}requestPOST /api/sessionCreate a session pinned to resolved runtime context.
session.delete{ sessionID: SessionID }sessionDELETE /api/session/:sessionIDDelete a session.
session.diff{ sessionID: SessionID }sessionGET /api/session/:sessionID/diffReturn session diff summary.
session.get{ sessionID: SessionID }sessionGET /api/session/:sessionIDGet one session.
session.list{ + limit?: number + order?: "asc" | "desc" + path?: string + roots?: boolean + start?: number + search?: string + cursor?: string +}requestGET /api/sessionList sessions for the current runtime context by default.
session.message.list{ + sessionID: SessionID + limit?: number + order?: "asc" | "desc" + cursor?: string +}sessionGET /api/session/:sessionID/messagePage through session messages.
session.prompt{ + sessionID: SessionID + prompt: Prompt + delivery?: "immediate" | "deferred" +}sessionPOST /api/session/:sessionID/promptCreate a user message and queue the agent loop.
session.todo{ sessionID: SessionID }sessionGET /api/session/:sessionID/todoReturn todos associated with the session.
session.update{ + sessionID: SessionID + title?: string + archived?: number + permission?: PermissionRule[] +}sessionPATCH /api/session/:sessionIDUpdate title, archival state, or session metadata.
session.wait{ sessionID: SessionID }sessionPOST /api/session/:sessionID/waitWait until the session is idle.
skill.list{}requestGET /api/skillAvailable skills.
vcs.diff{ + format?: "json" | "patch" + mode?: "worktree" | "default" +}requestGET /api/vcs/diffDiff for the runtime directory.
vcs.get{}requestGET /api/vcsVCS metadata.
vcs.patch{ patch: string }requestPOST /api/vcs/patchApply a patch to the runtime directory.
vcs.status{}requestGET /api/vcs/statusChanged files.
workspace.create{ + projectID?: ProjectID + name?: string + directory?: string + type: string + metadata?: Record<string, unknown> +}serverPOST /api/workspaceCreate or register a workspace.
workspace.delete{ workspaceID: WorkspaceID }serverDELETE /api/workspace/:workspaceIDRemove a workspace registration.
workspace.get{ workspaceID: WorkspaceID }serverGET /api/workspace/:workspaceIDGet workspace metadata.
workspace.list{ projectID?: ProjectID }serverGET /api/workspaceList workspaces, optionally filtered by project.
workspace.status{}serverGET /api/workspace/statusConnection/lifecycle status for all workspaces. Needs team discussion.
workspace.sync{}serverPOST /api/workspace/syncSync workspace metadata from adapters. Needs team discussion.
workspace.update{ + workspaceID: WorkspaceID + name?: string + metadata?: Record<string, unknown> + archived?: boolean +}serverPATCH /api/workspace/:workspaceIDUpdate workspace metadata or lifecycle state.
workspace.warp{ + workspaceID?: WorkspaceID + sessionID: SessionID + copyChanges: boolean +}serverPOST /api/workspace/warpMove a session into or out of a workspace. Needs team discussion.
+
+
+ +
+
+

Event Envelope

+

+ Every event uses the same envelope. Resource identity belongs in payload. Runtime identity belongs + in context. +

+
+
type ApiEvent<Payload> = {
+  id: string
+  type: string
+  time: number
+  context: {
+    directory: string
+    workspaceID?: string
+  }
+  payload: Payload
+}
+
{
+  "id": "evt_01",
+  "type": "message.part.delta",
+  "time": 1760000000000,
+  "context": {
+    "directory": "/repo/app",
+    "workspaceID": "ws_123"
+  },
+  "payload": {
+    "sessionID": "ses_123",
+    "messageID": "msg_456",
+    "partID": "part_789",
+    "field": "text",
+    "delta": "hello"
+  }
+}
+
+
+
+ +
+
+

Frontend Sync Store

+

+ A frontend can keep one giant store like the current TUI. Runtime data is partitioned by + contextKey. Durable entities such as sessions and messages are keyed by their own IDs. +

+
type RuntimeContext = {
+  directory: string
+  workspaceID?: string
+}
+
+type ContextKey = string
+type SessionID = string
+type MessageID = string
+
+type SyncStore = {
+  status: "loading" | "partial" | "complete"
+
+  shared: {
+    provider: Provider[]
+    provider_default: Record<string, string>
+    provider_next: ProviderListResponse
+    provider_auth: Record<string, ProviderAuthMethod[]>
+    console_state: ConsoleState
+  }
+
+  contexts: Record<
+    ContextKey,
+    {
+      context: RuntimeContext
+
+      config: Config
+      agent: Agent[]
+      command: Command[]
+      lsp: LspStatus[]
+      formatter: FormatterStatus[]
+      vcs: VcsInfo | undefined
+      mcp: Record<string, McpStatus>
+      mcp_resource: Record<string, McpResource>
+
+      session: SessionID[]
+      session_status: Record<SessionID, SessionStatus>
+    }
+  >
+
+  session: Record<SessionID, Session & { context: RuntimeContext }>
+  session_diff: Record<SessionID, Snapshot.FileDiff[]>
+  todo: Record<SessionID, Todo[]>
+  permission: Record<SessionID, PermissionRequest[]>
+  question: Record<SessionID, QuestionRequest[]>
+
+  message: Record<SessionID, Message[]>
+  part: Record<MessageID, Part[]>
+}
+
+function contextKey(context: RuntimeContext) {
+  return `${context.workspaceID ?? "local"}:${context.directory}`
+}
+
+
+
@@ -2239,9 +2234,7 @@ export default function Layout(props: ParentProps) { icon="plus-small" class="w-full" onClick={() => { - const item = project() - if (!item) return - void createWorkspace(item) + void createWorkspace(project) }} > {language.t("workspace.new")} @@ -2268,7 +2261,7 @@ export default function Layout(props: ParentProps) { From 1a28924ed89b98ee96e644d0e6e8e4a6a8bcfccc Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 11 May 2026 21:47:35 -0500 Subject: [PATCH 076/378] fix: grep external directory permission evaluation (#26958) --- packages/opencode/src/tool/grep.ts | 21 +++++----- packages/opencode/test/tool/grep.test.ts | 53 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index 4e89198df..01aa6a0b7 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -54,19 +54,20 @@ export const GrepTool = Tool.define( }) const ins = yield* InstanceState.context - const search = AppFileSystem.resolve( - path.isAbsolute(params.path ?? ins.directory) - ? (params.path ?? ins.directory) - : path.join(ins.directory, params.path ?? "."), - ) - yield* reference.ensure(search) + const requested = path.isAbsolute(params.path ?? ins.directory) + ? (params.path ?? ins.directory) + : path.join(ins.directory, params.path ?? ".") + yield* reference.ensure(requested) + const requestedInfo = yield* fs.stat(requested).pipe(Effect.catch(() => Effect.succeed(undefined))) + yield* assertExternalDirectoryEffect(ctx, requested, { + bypass: yield* reference.contains(requested), + kind: requestedInfo?.type === "Directory" ? "directory" : "file", + }) + + const search = AppFileSystem.resolve(requested) const info = yield* fs.stat(search).pipe(Effect.catch(() => Effect.succeed(undefined))) const cwd = info?.type === "Directory" ? search : path.dirname(search) const file = info?.type === "Directory" ? undefined : [path.relative(cwd, search)] - yield* assertExternalDirectoryEffect(ctx, search, { - bypass: yield* reference.contains(search), - kind: info?.type === "Directory" ? "directory" : "file", - }) const result = yield* rg.search({ cwd, diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 53f5d9a19..29b5a60db 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -1,4 +1,6 @@ import { describe, expect } from "bun:test" +import fs from "fs/promises" +import os from "os" import path from "path" import { Effect, Layer } from "effect" import { GrepTool } from "../../src/tool/grep" @@ -11,6 +13,8 @@ import { Ripgrep } from "../../src/file/ripgrep" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { testEffect } from "../lib/effect" import { Reference } from "@/reference/reference" +import { Permission } from "../../src/permission" +import type * as Tool from "../../src/tool/tool" const it = testEffect( Layer.mergeAll( @@ -110,4 +114,53 @@ describe("tool.grep", () => { expect(result.output).toContain("Line 2: line2") }), ) + + it.instance("does not ask for external_directory when alias path is allowed", () => + Effect.gen(function* () { + if (process.platform === "win32") return + + yield* TestInstance + const tmp = yield* Effect.acquireRelease( + Effect.promise(() => fs.mkdtemp(path.join(os.tmpdir(), "opencode-grep-alias-"))), + (dir) => Effect.promise(() => fs.rm(dir, { recursive: true, force: true })), + ) + const real = path.join(tmp, "real") + const alias = path.join(tmp, "alias") + yield* Effect.promise(() => fs.mkdir(real)) + yield* Effect.promise(() => fs.symlink(real, alias, "dir")) + yield* Effect.promise(() => Bun.write(path.join(real, "test.txt"), "needle")) + + const ruleset = Permission.fromConfig({ + grep: "allow", + external_directory: { + [path.join(alias, "*")]: "allow", + }, + }) + const requests: Array> = [] + const next: Tool.Context = { + ...ctx, + ask: (req) => + Effect.sync(() => { + const needsAsk = req.patterns.some( + (pattern) => Permission.evaluate(req.permission, pattern, ruleset).action !== "allow", + ) + if (needsAsk) requests.push(req) + }), + } + + const info = yield* GrepTool + const grep = yield* info.init() + const result = yield* grep.execute( + { + pattern: "needle", + path: alias, + include: "*.txt", + }, + next, + ) + + expect(result.metadata.matches).toBe(1) + expect(requests.find((req) => req.permission === "external_directory")).toBeUndefined() + }), + ) }) From ddce77622576f2800d7c79b25ad9c15de8b3a289 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 11 May 2026 22:25:41 -0500 Subject: [PATCH 077/378] ignore: add codebase skill to repo (#26990) --- .../DEEPENING.md | 37 ++++++++++ .../INTERFACE-DESIGN.md | 44 ++++++++++++ .../improve-codebase-architecture/LANGUAGE.md | 53 ++++++++++++++ .../improve-codebase-architecture/SKILL.md | 71 +++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 .opencode/skills/improve-codebase-architecture/DEEPENING.md create mode 100644 .opencode/skills/improve-codebase-architecture/INTERFACE-DESIGN.md create mode 100644 .opencode/skills/improve-codebase-architecture/LANGUAGE.md create mode 100644 .opencode/skills/improve-codebase-architecture/SKILL.md diff --git a/.opencode/skills/improve-codebase-architecture/DEEPENING.md b/.opencode/skills/improve-codebase-architecture/DEEPENING.md new file mode 100644 index 000000000..ecaf5d7dc --- /dev/null +++ b/.opencode/skills/improve-codebase-architecture/DEEPENING.md @@ -0,0 +1,37 @@ +# Deepening + +How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in [LANGUAGE.md](LANGUAGE.md) — **module**, **interface**, **seam**, **adapter**. + +## Dependency categories + +When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested across its seam. + +### 1. In-process + +Pure computation, in-memory state, no I/O. Always deepenable — merge the modules and test through the new interface directly. No adapter needed. + +### 2. Local-substitutable + +Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal; no port at the module's external interface. + +### 3. Remote but owned (Ports & Adapters) + +Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter. + +Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."* + +### 4. True external (Mock) + +Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter. + +## Seam discipline + +- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a port unless at least two adapters are justified (typically production + test). A single-adapter seam is just indirection. +- **Internal seams vs external seams.** A deep module can have internal seams (private to its implementation, used by its own tests) as well as the external seam at its interface. Don't expose internal seams through the interface just because tests use them. + +## Testing strategy: replace, don't layer + +- Old unit tests on shallow modules become waste once tests at the deepened module's interface exist — delete them. +- Write new tests at the deepened module's interface. The **interface is the test surface**. +- Tests assert on observable outcomes through the interface, not internal state. +- Tests should survive internal refactors — they describe behaviour, not implementation. If a test has to change when the implementation changes, it's testing past the interface. diff --git a/.opencode/skills/improve-codebase-architecture/INTERFACE-DESIGN.md b/.opencode/skills/improve-codebase-architecture/INTERFACE-DESIGN.md new file mode 100644 index 000000000..3197723a0 --- /dev/null +++ b/.opencode/skills/improve-codebase-architecture/INTERFACE-DESIGN.md @@ -0,0 +1,44 @@ +# Interface Design + +When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best. + +Uses the vocabulary in [LANGUAGE.md](LANGUAGE.md) — **module**, **interface**, **seam**, **adapter**, **leverage**. + +## Process + +### 1. Frame the problem space + +Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate: + +- The constraints any new interface would need to satisfy +- The dependencies it would rely on, and which category they fall into (see [DEEPENING.md](DEEPENING.md)) +- A rough illustrative code sketch to ground the constraints — not a proposal, just a way to make the constraints concrete + +Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel. + +### 2. Spawn sub-agents + +Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module. + +Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint: + +- Agent 1: "Minimize the interface — aim for 1–3 entry points max. Maximise leverage per entry point." +- Agent 2: "Maximise flexibility — support many use cases and extension." +- Agent 3: "Optimise for the most common caller — make the default case trivial." +- Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies." + +Include both [LANGUAGE.md](LANGUAGE.md) vocabulary and CONTEXT.md vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language. + +Each sub-agent outputs: + +1. Interface (types, methods, params — plus invariants, ordering, error modes) +2. Usage example showing how callers use it +3. What the implementation hides behind the seam +4. Dependency strategy and adapters (see [DEEPENING.md](DEEPENING.md)) +5. Trade-offs — where leverage is high, where it's thin + +### 3. Present and compare + +Present designs sequentially so the user can absorb each one, then compare them in prose. Contrast by **depth** (leverage at the interface), **locality** (where change concentrates), and **seam placement**. + +After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu. diff --git a/.opencode/skills/improve-codebase-architecture/LANGUAGE.md b/.opencode/skills/improve-codebase-architecture/LANGUAGE.md new file mode 100644 index 000000000..530c27630 --- /dev/null +++ b/.opencode/skills/improve-codebase-architecture/LANGUAGE.md @@ -0,0 +1,53 @@ +# Language + +Shared vocabulary for every suggestion this skill makes. Use these terms exactly — don't substitute "component," "service," "API," or "boundary." Consistent language is the whole point. + +## Terms + +**Module** +Anything with an interface and an implementation. Deliberately scale-agnostic — applies equally to a function, class, package, or tier-spanning slice. +_Avoid_: unit, component, service. + +**Interface** +Everything a caller must know to use the module correctly. Includes the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics. +_Avoid_: API, signature (too narrow — those refer only to the type-level surface). + +**Implementation** +What's inside a module — its body of code. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (a Postgres repo) or a large adapter with a small implementation (an in-memory fake). Reach for "adapter" when the seam is the topic; "implementation" otherwise. + +**Depth** +Leverage at the interface — the amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour sits behind a small interface. A module is **shallow** when the interface is nearly as complex as the implementation. + +**Seam** _(from Michael Feathers)_ +A place where you can alter behaviour without editing in that place. The *location* at which a module's interface lives. Choosing where to put the seam is its own design decision, distinct from what goes behind it. +_Avoid_: boundary (overloaded with DDD's bounded context). + +**Adapter** +A concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside). + +**Leverage** +What callers get from depth. More capability per unit of interface they have to learn. One implementation pays back across N call sites and M tests. + +**Locality** +What maintainers get from depth. Change, bugs, knowledge, and verification concentrate at one place rather than spreading across callers. Fix once, fixed everywhere. + +## Principles + +- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts — they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface. +- **The deletion test.** Imagine deleting the module. If complexity vanishes, the module wasn't hiding anything (it was a pass-through). If complexity reappears across N callers, the module was earning its keep. +- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape. +- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it. + +## Relationships + +- A **Module** has exactly one **Interface** (the surface it presents to callers and tests). +- **Depth** is a property of a **Module**, measured against its **Interface**. +- A **Seam** is where a **Module**'s **Interface** lives. +- An **Adapter** sits at a **Seam** and satisfies the **Interface**. +- **Depth** produces **Leverage** for callers and **Locality** for maintainers. + +## Rejected framings + +- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead. +- **"Interface" as the TypeScript `interface` keyword or a class's public methods**: too narrow — interface here includes every fact a caller must know. +- **"Boundary"**: overloaded with DDD's bounded context. Say **seam** or **interface**. diff --git a/.opencode/skills/improve-codebase-architecture/SKILL.md b/.opencode/skills/improve-codebase-architecture/SKILL.md new file mode 100644 index 000000000..05984a609 --- /dev/null +++ b/.opencode/skills/improve-codebase-architecture/SKILL.md @@ -0,0 +1,71 @@ +--- +name: improve-codebase-architecture +description: Find deepening opportunities in a codebase, informed by the domain language in CONTEXT.md and the decisions in docs/adr/. Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable. +--- + +# Improve Codebase Architecture + +Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability. + +## Glossary + +Use these terms exactly in every suggestion. Consistent language is the point — don't drift into "component," "service," "API," or "boundary." Full definitions in [LANGUAGE.md](LANGUAGE.md). + +- **Module** — anything with an interface and an implementation (function, class, package, slice). +- **Interface** — everything a caller must know to use the module: types, invariants, error modes, ordering, config. Not just the type signature. +- **Implementation** — the code inside. +- **Depth** — leverage at the interface: a lot of behaviour behind a small interface. **Deep** = high leverage. **Shallow** = interface nearly as complex as the implementation. +- **Seam** — where an interface lives; a place behaviour can be altered without editing in place. (Use this, not "boundary.") +- **Adapter** — a concrete thing satisfying an interface at a seam. +- **Leverage** — what callers get from depth. +- **Locality** — what maintainers get from depth: change, bugs, knowledge concentrated in one place. + +Key principles (see [LANGUAGE.md](LANGUAGE.md) for the full list): + +- **Deletion test**: imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep. +- **The interface is the test surface.** +- **One adapter = hypothetical seam. Two adapters = real seam.** + +This skill is _informed_ by the project's domain model. The domain language gives names to good seams; ADRs record decisions the skill should not re-litigate. + +## Process + +### 1. Explore + +Read the project's domain glossary and any ADRs in the area you're touching first. + +Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction: + +- Where does understanding one concept require bouncing between many small modules? +- Where are modules **shallow** — interface nearly as complex as the implementation? +- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)? +- Where do tightly-coupled modules leak across their seams? +- Which parts of the codebase are untested, or hard to test through their current interface? + +Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want. + +### 2. Present candidates + +Present a numbered list of deepening opportunities. For each candidate: + +- **Files** — which files/modules are involved +- **Problem** — why the current architecture is causing friction +- **Solution** — plain English description of what would change +- **Benefits** — explained in terms of locality and leverage, and also in how tests would improve + +**Use CONTEXT.md vocabulary for the domain, and [LANGUAGE.md](LANGUAGE.md) vocabulary for the architecture.** If `CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service." + +**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly (e.g. _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids. + +Do NOT propose interfaces yet. Ask the user: "Which of these would you like to explore?" + +### 3. Grilling loop + +Once the user picks a candidate, drop into a grilling conversation. Walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive. + +Side effects happen inline as decisions crystallize: + +- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md` — same discipline as `/grill-with-docs` (see [CONTEXT-FORMAT.md](../grill-with-docs/CONTEXT-FORMAT.md)). Create the file lazily if it doesn't exist. +- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there. +- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See [ADR-FORMAT.md](../grill-with-docs/ADR-FORMAT.md). +- **Want to explore alternative interfaces for the deepened module?** See [INTERFACE-DESIGN.md](INTERFACE-DESIGN.md). From 591eb667d5c3c28034bea0b2d3870625feb809eb Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 03:26:47 +0000 Subject: [PATCH 078/378] chore: generate --- .opencode/skills/improve-codebase-architecture/DEEPENING.md | 2 +- .opencode/skills/improve-codebase-architecture/LANGUAGE.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.opencode/skills/improve-codebase-architecture/DEEPENING.md b/.opencode/skills/improve-codebase-architecture/DEEPENING.md index ecaf5d7dc..c52fdfd99 100644 --- a/.opencode/skills/improve-codebase-architecture/DEEPENING.md +++ b/.opencode/skills/improve-codebase-architecture/DEEPENING.md @@ -18,7 +18,7 @@ Dependencies that have local test stand-ins (PGLite for Postgres, in-memory file Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter. -Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."* +Recommendation shape: _"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."_ ### 4. True external (Mock) diff --git a/.opencode/skills/improve-codebase-architecture/LANGUAGE.md b/.opencode/skills/improve-codebase-architecture/LANGUAGE.md index 530c27630..dd9b60fea 100644 --- a/.opencode/skills/improve-codebase-architecture/LANGUAGE.md +++ b/.opencode/skills/improve-codebase-architecture/LANGUAGE.md @@ -19,11 +19,11 @@ What's inside a module — its body of code. Distinct from **Adapter**: a thing Leverage at the interface — the amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour sits behind a small interface. A module is **shallow** when the interface is nearly as complex as the implementation. **Seam** _(from Michael Feathers)_ -A place where you can alter behaviour without editing in that place. The *location* at which a module's interface lives. Choosing where to put the seam is its own design decision, distinct from what goes behind it. +A place where you can alter behaviour without editing in that place. The _location_ at which a module's interface lives. Choosing where to put the seam is its own design decision, distinct from what goes behind it. _Avoid_: boundary (overloaded with DDD's bounded context). **Adapter** -A concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside). +A concrete thing that satisfies an interface at a seam. Describes _role_ (what slot it fills), not substance (what's inside). **Leverage** What callers get from depth. More capability per unit of interface they have to learn. One implementation pays back across N call sites and M tests. @@ -35,7 +35,7 @@ What maintainers get from depth. Change, bugs, knowledge, and verification conce - **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts — they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface. - **The deletion test.** Imagine deleting the module. If complexity vanishes, the module wasn't hiding anything (it was a pass-through). If complexity reappears across N callers, the module was earning its keep. -- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape. +- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test _past_ the interface, the module is probably the wrong shape. - **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it. ## Relationships From 2b432d9e036ef5871e169b00816cb5e5ddf3a77c Mon Sep 17 00:00:00 2001 From: James Long Date: Mon, 11 May 2026 23:42:04 -0400 Subject: [PATCH 079/378] fix(tui): scope events by project (#26936) --- .../opencode/src/cli/cmd/tui/context/event.ts | 32 ++++------ .../opencode/src/cli/cmd/tui/context/sync.tsx | 6 +- .../test/cli/cmd/tui/sync-fixture.tsx | 33 ++++++++-- .../opencode/test/cli/cmd/tui/sync.test.tsx | 42 ++++++++++++- .../opencode/test/cli/tui/use-event.test.tsx | 63 +++++++++++-------- 5 files changed, 122 insertions(+), 54 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/event.ts b/packages/opencode/src/cli/cmd/tui/context/event.ts index 156f9c947..5d814ecdc 100644 --- a/packages/opencode/src/cli/cmd/tui/context/event.ts +++ b/packages/opencode/src/cli/cmd/tui/context/event.ts @@ -2,39 +2,33 @@ import type { Event } from "@opencode-ai/sdk/v2" import { useProject } from "./project" import { useSDK } from "./sdk" +type EventMetadata = { + workspace: string | undefined +} + export function useEvent() { const project = useProject() const sdk = useSDK() - function subscribe(handler: (event: Event) => void) { + function subscribe(handler: (event: Event, metadata: EventMetadata) => void) { return sdk.event.on("event", (event) => { if (event.payload.type === "sync") { return } - // Special hack for truly global events - if (event.directory === "global") { - handler(event.payload) - } - - if (project.workspace.current()) { - if (event.workspace === project.workspace.current()) { - handler(event.payload) - } - - return - } - - if (event.directory === project.instance.directory()) { - handler(event.payload) + if (event.directory === "global" || event.project === project.project()) { + handler(event.payload, { workspace: event.workspace }) } }) } - function on(type: T, handler: (event: Extract) => void) { - return subscribe((event) => { + function on( + type: T, + handler: (event: Extract, metadata: EventMetadata) => void, + ) { + return subscribe((event: Event, metadata: EventMetadata) => { if (event.type !== type) return - handler(event as Extract) + handler(event as Extract, metadata) }) } diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 0d4cb2e6e..76b1807ab 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -131,7 +131,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ .then((x) => (x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id))) } - event.subscribe((event) => { + event.subscribe((event, { workspace }) => { switch (event.type) { case "server.instance.disposed": void bootstrap() @@ -364,7 +364,9 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ } case "vcs.branch.updated": { - setStore("vcs", { branch: event.properties.branch }) + if (workspace === project.workspace.current()) { + setStore("vcs", { branch: event.properties.branch }) + } break } } diff --git a/packages/opencode/test/cli/cmd/tui/sync-fixture.tsx b/packages/opencode/test/cli/cmd/tui/sync-fixture.tsx index d9ecdbe9d..5f51374c1 100644 --- a/packages/opencode/test/cli/cmd/tui/sync-fixture.tsx +++ b/packages/opencode/test/cli/cmd/tui/sync-fixture.tsx @@ -4,9 +4,10 @@ import { onMount } from "solid-js" import { ArgsProvider } from "../../../../src/cli/cmd/tui/context/args" import { ExitProvider } from "../../../../src/cli/cmd/tui/context/exit" import { KVProvider, useKV } from "../../../../src/cli/cmd/tui/context/kv" -import { ProjectProvider } from "../../../../src/cli/cmd/tui/context/project" +import { ProjectProvider, useProject } from "../../../../src/cli/cmd/tui/context/project" import { SDKProvider, type EventSource } from "../../../../src/cli/cmd/tui/context/sdk" import { SyncProvider, useSync } from "../../../../src/cli/cmd/tui/context/sync" +import type { GlobalEvent } from "@opencode-ai/sdk/v2" export const worktree = "/tmp/opencode" export const directory = `${worktree}/packages/opencode` @@ -30,6 +31,25 @@ export function eventSource(): EventSource { return { subscribe: async () => () => {} } } +export function createEventSource() { + let fn: ((event: GlobalEvent) => void) | undefined + + return { + source: { + subscribe: async (handler: (event: GlobalEvent) => void) => { + fn = handler + return () => { + if (fn === handler) fn = undefined + } + }, + } satisfies EventSource, + emit(event: GlobalEvent) { + if (!fn) throw new Error("event source not ready") + fn(event) + }, + } +} + type FetchHandler = (url: URL) => Response | Promise | undefined export function createFetch(override?: FetchHandler) { @@ -77,11 +97,13 @@ export function createFetch(override?: FetchHandler) { return { fetch, session } } -type Ctx = { kv: ReturnType; sync: ReturnType } +type Ctx = { kv: ReturnType; project: ReturnType; sync: ReturnType } export async function mount(override?: FetchHandler) { const calls = createFetch(override) + const events = createEventSource() let sync!: ReturnType + let project!: ReturnType let kv!: ReturnType let done!: () => void const ready = new Promise((resolve) => { @@ -89,9 +111,10 @@ export async function mount(override?: FetchHandler) { }) function Probe() { - const ctx: Ctx = { kv: useKV(), sync: useSync() } + const ctx: Ctx = { kv: useKV(), project: useProject(), sync: useSync() } onMount(() => { sync = ctx.sync + project = ctx.project kv = ctx.kv done() }) @@ -102,7 +125,7 @@ export async function mount(override?: FetchHandler) { - + @@ -116,5 +139,5 @@ export async function mount(override?: FetchHandler) { await ready await wait(() => sync.status === "complete") - return { app, kv, sync, session: calls.session } + return { app, emit: events.emit, kv, project, sync, session: calls.session } } diff --git a/packages/opencode/test/cli/cmd/tui/sync.test.tsx b/packages/opencode/test/cli/cmd/tui/sync.test.tsx index f67257f6c..714c39a78 100644 --- a/packages/opencode/test/cli/cmd/tui/sync.test.tsx +++ b/packages/opencode/test/cli/cmd/tui/sync.test.tsx @@ -2,7 +2,21 @@ import { describe, expect, test } from "bun:test" import { Global } from "@opencode-ai/core/global" import { tmpdir } from "../../../fixture/fixture" -import { mount } from "./sync-fixture" +import { mount, wait } from "./sync-fixture" +import type { GlobalEvent } from "@opencode-ai/sdk/v2" + +function branchEvent(branch: string, workspace?: string): GlobalEvent { + return { + directory: "/tmp/other", + project: "proj_test", + workspace, + payload: { + id: `evt_vcs_${branch}`, + type: "vcs.branch.updated", + properties: { branch }, + }, + } +} describe("tui sync", () => { test("refresh scopes sessions by default and lists project sessions when disabled", async () => { @@ -27,4 +41,30 @@ describe("tui sync", () => { Global.Path.state = previous } }) + + test("vcs branch updates only apply for the active workspace", async () => { + const previous = Global.Path.state + await using tmp = await tmpdir() + Global.Path.state = tmp.path + await Bun.write(`${tmp.path}/kv.json`, "{}") + const { app, emit, project, sync } = await mount() + + try { + expect(sync.data.vcs?.branch).toBe("main") + + project.workspace.set("ws_a") + emit(branchEvent("other", "ws_b")) + await Bun.sleep(30) + + expect(sync.data.vcs?.branch).toBe("main") + + emit(branchEvent("feature", "ws_a")) + await wait(() => sync.data.vcs?.branch === "feature") + + expect(sync.data.vcs?.branch).toBe("feature") + } finally { + app.renderer.destroy() + Global.Path.state = previous + } + }) }) diff --git a/packages/opencode/test/cli/tui/use-event.test.tsx b/packages/opencode/test/cli/tui/use-event.test.tsx index 78253361b..ac2d942db 100644 --- a/packages/opencode/test/cli/tui/use-event.test.tsx +++ b/packages/opencode/test/cli/tui/use-event.test.tsx @@ -7,6 +7,8 @@ import { ProjectProvider, useProject } from "../../../src/cli/cmd/tui/context/pr import { SDKProvider } from "../../../src/cli/cmd/tui/context/sdk" import { useEvent } from "../../../src/cli/cmd/tui/context/event" +const projectID = "proj_test" + async function wait(fn: () => boolean, timeout = 2000) { const start = Date.now() while (!fn()) { @@ -15,9 +17,10 @@ async function wait(fn: () => boolean, timeout = 2000) { } } -function event(payload: Event, input: { directory: string; workspace?: string }): GlobalEvent { +function event(payload: Event, input: { directory: string; project?: string; workspace?: string }): GlobalEvent { return { directory: input.directory, + project: input.project, workspace: input.workspace, payload, } @@ -65,6 +68,13 @@ function createSource() { async function mount() { const source = createSource() const seen: Event[] = [] + const workspaces: Array = [] + const fetch = (async (input: RequestInfo | URL) => { + const url = new URL(input instanceof Request ? input.url : String(input)) + if (url.pathname === "/path") return Response.json({ home: "", state: "", config: "", directory: "/tmp/root" }) + if (url.pathname === "/project/current") return Response.json({ id: projectID }) + throw new Error(`unexpected request: ${url.pathname}`) + }) as typeof globalThis.fetch let project!: ReturnType let done!: () => void const ready = new Promise((resolve) => { @@ -72,30 +82,42 @@ async function mount() { }) const app = await testRender(() => ( - + { + onReady={async (ctx) => { project = ctx.project + await project.sync() done() }} seen={seen} + workspaces={workspaces} /> )) await ready - return { app, emit: source.emit, project, seen } + return { app, emit: source.emit, project, seen, workspaces } } -function Probe(props: { seen: Event[]; onReady: (ctx: { project: ReturnType }) => void }) { +function Probe(props: { + seen: Event[] + workspaces: Array + onReady: (ctx: { project: ReturnType }) => void +}) { const project = useProject() const event = useEvent() onMount(() => { - event.subscribe((evt) => { + event.subscribe((evt, { workspace }) => { props.seen.push(evt) + props.workspaces.push(workspace) }) props.onReady({ project }) }) @@ -104,25 +126,26 @@ function Probe(props: { seen: Event[]; onReady: (ctx: { project: ReturnType { - test("delivers matching directory events without an active workspace", async () => { - const { app, emit, seen } = await mount() + test("delivers events for the current project", async () => { + const { app, emit, seen, workspaces } = await mount() try { - emit(event(vcs("main"), { directory: "/tmp/root" })) + emit(event(vcs("main"), { directory: "/tmp/other", project: projectID, workspace: "ws_a" })) await wait(() => seen.length === 1) expect(seen).toEqual([vcs("main")]) + expect(workspaces).toEqual(["ws_a"]) } finally { app.renderer.destroy() } }) - test("ignores non-matching directory events without an active workspace", async () => { + test("ignores events for other projects", async () => { const { app, emit, seen } = await mount() try { - emit(event(vcs("other"), { directory: "/tmp/other" })) + emit(event(vcs("other"), { directory: "/tmp/root", project: "proj_other" })) await Bun.sleep(30) expect(seen).toHaveLength(0) @@ -131,12 +154,12 @@ describe("useEvent", () => { } }) - test("delivers matching workspace events when a workspace is active", async () => { + test("delivers current project events regardless of active workspace", async () => { const { app, emit, project, seen } = await mount() try { project.workspace.set("ws_a") - emit(event(vcs("ws"), { directory: "/tmp/other", workspace: "ws_a" })) + emit(event(vcs("ws"), { directory: "/tmp/other", project: projectID, workspace: "ws_b" })) await wait(() => seen.length === 1) @@ -146,20 +169,6 @@ describe("useEvent", () => { } }) - test("ignores non-matching workspace events when a workspace is active", async () => { - const { app, emit, project, seen } = await mount() - - try { - project.workspace.set("ws_a") - emit(event(vcs("ws"), { directory: "/tmp/root", workspace: "ws_b" })) - await Bun.sleep(30) - - expect(seen).toHaveLength(0) - } finally { - app.renderer.destroy() - } - }) - test("delivers truly global events even when a workspace is active", async () => { const { app, emit, project, seen } = await mount() From 5cc84800dc236353c839d845ae2403482d3bac11 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 03:43:40 +0000 Subject: [PATCH 080/378] chore: generate --- packages/opencode/test/cli/tui/use-event.test.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/opencode/test/cli/tui/use-event.test.tsx b/packages/opencode/test/cli/tui/use-event.test.tsx index ac2d942db..d690cfd6c 100644 --- a/packages/opencode/test/cli/tui/use-event.test.tsx +++ b/packages/opencode/test/cli/tui/use-event.test.tsx @@ -82,12 +82,7 @@ async function mount() { }) const app = await testRender(() => ( - + { From 487575773d2d51fb541eea1fb40f647a3bbf62cf Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 11 May 2026 23:13:22 -0500 Subject: [PATCH 081/378] feat: create global opencode.jsonc if no configs exist (#26992) --- packages/opencode/src/config/config.ts | 10 ++++ packages/opencode/test/config/config.test.ts | 48 ++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 4b10665ac..e44405f42 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -409,6 +409,16 @@ export const layer = Layer.effect( const loadGlobal = Effect.fnUntraced(function* () { let result: Info = {} + // Seed the default global config with the schema for editor completion, but avoid writing when the user + // explicitly routes config through env-provided paths or content. + if (!Flag.OPENCODE_CONFIG && !Flag.OPENCODE_CONFIG_DIR && !Flag.OPENCODE_CONFIG_CONTENT) { + const file = globalConfigFile() + if (!existsSync(file)) { + yield* fs + .writeWithDirs(file, JSON.stringify({ $schema: "https://opencode.ai/config.json" }, null, 2)) + .pipe(Effect.catch(() => Effect.void)) + } + } result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "config.json"))) result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.json"))) result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.jsonc"))) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index fa9fd332e..90e78efcd 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -141,6 +141,54 @@ test("loads config with defaults when no files exist", async () => { }) }) +test("creates global jsonc config with schema when no global configs exist", async () => { + await using tmp = await tmpdir() + const prev = Global.Path.config + ;(Global.Path as { config: string }).config = tmp.path + await clear(true) + + try { + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + await load() + }, + }) + + const content = await Filesystem.readText(path.join(tmp.path, "opencode.jsonc")) + expect(content).toContain('"$schema": "https://opencode.ai/config.json"') + } finally { + ;(Global.Path as { config: string }).config = prev + await clear(true) + } +}) + +test("does not create global config when OPENCODE_CONFIG_DIR is set", async () => { + await using tmp = await tmpdir() + await using custom = await tmpdir() + const prevConfig = Global.Path.config + const prevEnv = process.env.OPENCODE_CONFIG_DIR + ;(Global.Path as { config: string }).config = tmp.path + process.env.OPENCODE_CONFIG_DIR = custom.path + await clear(true) + + try { + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + await load() + }, + }) + + expect(await Filesystem.exists(path.join(tmp.path, "opencode.jsonc"))).toBe(false) + } finally { + ;(Global.Path as { config: string }).config = prevConfig + if (prevEnv === undefined) delete process.env.OPENCODE_CONFIG_DIR + else process.env.OPENCODE_CONFIG_DIR = prevEnv + await clear(true) + } +}) + test("loads JSON config file", async () => { await using tmp = await tmpdir({ init: async (dir) => { From e36bc20f844fe3aa1ee581502cc921ebba072d3d Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 12 May 2026 00:30:03 -0400 Subject: [PATCH 082/378] fix(tui): fix flicker by avoiding redundant workspace session sync (#26997) --- packages/opencode/src/cli/cmd/tui/context/sync.tsx | 5 ----- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 76b1807ab..31104ddd9 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -113,7 +113,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ const kv = useKV() const fullSyncedSessions = new Set() - let syncedWorkspace = project.workspace.current() function sessionListQuery(): { scope?: "project"; path?: string } { if (!kv.get("session_directory_filter_enabled", true)) return { scope: "project" } @@ -378,10 +377,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ async function bootstrap(input: { fatal?: boolean } = {}) { const fatal = input.fatal ?? true const workspace = project.workspace.current() - if (workspace !== syncedWorkspace) { - fullSyncedSessions.clear() - syncedWorkspace = workspace - } const projectPromise = project.sync() const sessionListPromise = projectPromise.then(() => listSessions()) 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 b2ee3af62..3e966d9a5 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -10,6 +10,7 @@ import { onMount, Show, Switch, + untrack, useContext, } from "solid-js" import { Dynamic } from "solid-js/web" @@ -242,7 +243,7 @@ export function Session() { createEffect(() => { const sessionID = route.sessionID void (async () => { - const previousWorkspace = project.workspace.current() + const previousWorkspace = untrack(() => project.workspace.current()) const result = await sdk.client.session.get({ sessionID }, { throwOnError: true }) if (!result.data) { toast.show({ From 36d40fee4dec052e5c81664390e61a74705dfa13 Mon Sep 17 00:00:00 2001 From: Dax Date: Tue, 12 May 2026 01:18:57 -0400 Subject: [PATCH 083/378] Track session usage totals (#26644) --- .opencode/opencode.jsonc | 6 +- .../migration.sql | 6 + .../snapshot.json | 1591 +++++++++++++++++ packages/opencode/src/cli/cmd/stats.ts | 12 +- .../cli/cmd/tui/component/prompt/index.tsx | 3 +- .../opencode/src/cli/cmd/tui/context/sync.tsx | 3 +- .../tui/feature-plugins/sidebar/context.tsx | 3 +- .../opencode/src/cli/cmd/tui/plugin/api.tsx | 3 + .../tui/routes/session/subagent-footer.tsx | 2 +- packages/opencode/src/data-migration.ts | 100 +- packages/opencode/src/session/projectors.ts | 51 + packages/opencode/src/session/session.sql.ts | 10 +- packages/opencode/src/session/session.ts | 34 + .../opencode/src/storage/json-migration.ts | 6 + packages/opencode/src/v2/session.ts | 20 + packages/opencode/test/effect/runner.test.ts | 13 +- packages/opencode/test/fixture/tui-plugin.ts | 1 + .../test/session/session-schema.test.ts | 2 + packages/plugin/src/tui.ts | 2 + packages/sdk/js/src/v2/gen/types.gen.ts | 40 + 20 files changed, 1882 insertions(+), 26 deletions(-) create mode 100644 packages/opencode/migration/20260510033149_session_usage/migration.sql create mode 100644 packages/opencode/migration/20260510033149_session_usage/snapshot.json diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index dab531d33..0ae2fbe26 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -1,11 +1,7 @@ { "$schema": "https://opencode.ai/config.json", "provider": {}, - "permission": { - "edit": { - "packages/opencode/migration/*": "ask", - }, - }, + "permission": {}, "mcp": {}, "tools": { "github-triage": false, diff --git a/packages/opencode/migration/20260510033149_session_usage/migration.sql b/packages/opencode/migration/20260510033149_session_usage/migration.sql new file mode 100644 index 000000000..68e12aad0 --- /dev/null +++ b/packages/opencode/migration/20260510033149_session_usage/migration.sql @@ -0,0 +1,6 @@ +ALTER TABLE `session` ADD `cost` real DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE `session` ADD `tokens_input` integer DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE `session` ADD `tokens_output` integer DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE `session` ADD `tokens_reasoning` integer DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE `session` ADD `tokens_cache_read` integer DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE `session` ADD `tokens_cache_write` integer DEFAULT 0 NOT NULL; diff --git a/packages/opencode/migration/20260510033149_session_usage/snapshot.json b/packages/opencode/migration/20260510033149_session_usage/snapshot.json new file mode 100644 index 000000000..4ec5dbc52 --- /dev/null +++ b/packages/opencode/migration/20260510033149_session_usage/snapshot.json @@ -0,0 +1,1591 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "be5eae31-b7f8-4292-8827-c36a524abd1b", + "prevIds": [ + "630a93f2-c6c6-4191-a351-868d8f3a05d4" + ], + "ddl": [ + { + "name": "account_state", + "entityType": "tables" + }, + { + "name": "account", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session_message", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "name": "event_sequence", + "entityType": "tables" + }, + { + "name": "event", + "entityType": "tables" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_account_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_org_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": "''", + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "extra", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_used", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url_override", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "path", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "real", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "cost", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "tokens_input", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "tokens_output", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "tokens_reasoning", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "tokens_cache_read", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "tokens_cache_write", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "agent", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "aggregate_id", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seq", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "owner_id", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "aggregate_id", + "entityType": "columns", + "table": "event" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seq", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "event" + }, + { + "columns": [ + "active_account_id" + ], + "tableTo": "account", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "nameExplicit": false, + "name": "fk_account_state_active_account_id_account_id_fk", + "entityType": "fks", + "table": "account_state" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": [ + "message_id" + ], + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_message_session_id_session_id_fk", + "entityType": "fks", + "table": "session_message" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": [ + "aggregate_id" + ], + "tableTo": "event_sequence", + "columnsTo": [ + "aggregate_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_event_aggregate_id_event_sequence_aggregate_id_fk", + "entityType": "fks", + "table": "event" + }, + { + "columns": [ + "email", + "url" + ], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": [ + "session_id", + "position" + ], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_state_pk", + "table": "account_state", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_pk", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": [ + "project_id" + ], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_message_pk", + "table": "session_message", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": [ + "session_id" + ], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": [ + "aggregate_id" + ], + "nameExplicit": false, + "name": "event_sequence_pk", + "table": "event_sequence", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "event_pk", + "table": "event", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_time_created_id_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_id_id_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_message_session_idx", + "entityType": "indexes", + "table": "session_message" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "type", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_message_session_type_idx", + "entityType": "indexes", + "table": "session_message" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_message_time_created_idx", + "entityType": "indexes", + "table": "session_message" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_workspace_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts index 0124a2693..3dadea9dd 100644 --- a/packages/opencode/src/cli/cmd/stats.ts +++ b/packages/opencode/src/cli/cmd/stats.ts @@ -164,8 +164,8 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* ( Effect.gen(function* () { const messages = yield* svc.messages({ sessionID: session.id }) - let sessionCost = 0 - let sessionTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } + const sessionCost = session.cost ?? 0 + const sessionTokens = session.tokens ?? { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } let sessionToolUsage: Record = {} let sessionModelUsage: Record< string, @@ -178,8 +178,6 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* ( for (const message of messages) { if (message.info.role === "assistant") { - sessionCost += message.info.cost || 0 - const modelKey = `${message.info.providerID}/${message.info.modelID}` if (!sessionModelUsage[modelKey]) { sessionModelUsage[modelKey] = { @@ -192,12 +190,6 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* ( sessionModelUsage[modelKey].cost += message.info.cost || 0 if (message.info.tokens) { - sessionTokens.input += message.info.tokens.input || 0 - sessionTokens.output += message.info.tokens.output || 0 - sessionTokens.reasoning += message.info.tokens.reasoning || 0 - sessionTokens.cache.read += message.info.tokens.cache?.read || 0 - sessionTokens.cache.write += message.info.tokens.cache?.write || 0 - sessionModelUsage[modelKey].tokens.input += message.info.tokens.input || 0 sessionModelUsage[modelKey].tokens.output += (message.info.tokens.output || 0) + (message.info.tokens.reasoning || 0) 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 3bbfc261b..c80daf9cf 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -337,6 +337,7 @@ export function Prompt(props: PromptProps) { const usage = createMemo(() => { if (!props.sessionID) return + const session = sync.session.get(props.sessionID) const msg = sync.data.message[props.sessionID] ?? [] const last = msg.findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0) if (!last) return @@ -347,7 +348,7 @@ export function Prompt(props: PromptProps) { const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID] const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined - const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0) + const cost = session?.cost ?? 0 return { context: pct ? `${Locale.number(tokens)} (${pct})` : Locale.number(tokens), cost: cost > 0 ? money.format(cost) : undefined, diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 31104ddd9..9f8a384f7 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -345,7 +345,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ case "message.part.removed": { const parts = store.part[event.properties.messageID] const result = Binary.search(parts, event.properties.partID, (p) => p.id) - if (result.found) + if (result.found) { setStore( "part", event.properties.messageID, @@ -353,6 +353,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ draft.splice(result.index, 1) }), ) + } break } diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx index b3cf2beb4..405e8c145 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx @@ -13,7 +13,8 @@ const money = new Intl.NumberFormat("en-US", { function View(props: { api: TuiPluginApi; session_id: string }) { const theme = () => props.api.theme.current const msg = createMemo(() => props.api.state.session.messages(props.session_id)) - const cost = createMemo(() => msg().reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0)) + const session = createMemo(() => props.api.state.session.get(props.session_id)) + const cost = createMemo(() => session()?.cost ?? 0) const state = createMemo(() => { const last = msg().findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0) diff --git a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx index 54059f4a2..8958a9285 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx +++ b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx @@ -147,6 +147,9 @@ function stateApi(sync: ReturnType): TuiPluginApi["state"] { count() { return sync.data.session.length }, + get(sessionID) { + return sync.session.get(sessionID) + }, diff(sessionID) { return (sync.data.session_diff[sessionID] ?? []).flatMap((item) => item.file === undefined ? [] : [{ ...item, file: item.file }], diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx index 2a6813ffb..f4a458b63 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx @@ -42,7 +42,7 @@ export function SubagentFooter() { const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID] const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined - const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0) + const cost = session()?.cost ?? 0 const money = new Intl.NumberFormat("en-US", { style: "currency", diff --git a/packages/opencode/src/data-migration.ts b/packages/opencode/src/data-migration.ts index 0a2973de5..f2268b4c8 100644 --- a/packages/opencode/src/data-migration.ts +++ b/packages/opencode/src/data-migration.ts @@ -2,7 +2,9 @@ import { Context, Effect, Layer } from "effect" import { Database } from "./storage/db" import { DataMigrationTable } from "./data-migration.sql" import * as Log from "@opencode-ai/core/util/log" -import { eq } from "drizzle-orm" +import { and, asc, eq, gt, inArray, sql } from "drizzle-orm" +import { MessageTable, SessionTable } from "./session/session.sql" +import type { SessionID } from "./session/schema" export type Migration = { name: string @@ -18,7 +20,101 @@ export class Service extends Context.Service()("@opencode/Da export const layer = Layer.effect( Service, Effect.gen(function* () { - const migrations: Migration[] = [] + const migrations: Migration[] = [ + { + name: "session_usage_from_messages", + run: Effect.gen(function* () { + type Usage = { + cost: number + tokens: { input: number; output: number; reasoning: number; cache: { read: number; write: number } } + } + + for (let cursor: SessionID | undefined, page = 1; ; page++) { + const next = yield* Effect.gen(function* () { + const sessions = yield* Effect.sync(() => + Database.use((db) => + db + .select({ id: SessionTable.id }) + .from(SessionTable) + .where(cursor ? gt(SessionTable.id, cursor) : undefined) + .orderBy(asc(SessionTable.id)) + .limit(100) + .all(), + ), + ) + if (sessions.length === 0) return + + yield* Effect.sync(() => + Database.transaction((db) => { + const usageBySession = new Map( + sessions.map((session) => [ + session.id, + { cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } }, + ]), + ) + + for (const row of db + .select({ + session_id: MessageTable.session_id, + cost: sql`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.cost'), 0)), 0)`, + tokens_input: sql`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.input'), 0)), 0)`, + tokens_output: sql`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.output'), 0)), 0)`, + tokens_reasoning: sql`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.reasoning'), 0)), 0)`, + tokens_cache_read: sql`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.cache.read'), 0)), 0)`, + tokens_cache_write: sql`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.cache.write'), 0)), 0)`, + }) + .from(MessageTable) + .where( + and( + inArray(MessageTable.session_id, sessions.map((session) => session.id)), + sql`json_extract(${MessageTable.data}, '$.role') = 'assistant'`, + ), + ) + .groupBy(MessageTable.session_id) + .all()) { + const current = usageBySession.get(row.session_id) + if (!current) continue + current.cost = row.cost + current.tokens.input = row.tokens_input + current.tokens.output = row.tokens_output + current.tokens.reasoning = row.tokens_reasoning + current.tokens.cache.read = row.tokens_cache_read + current.tokens.cache.write = row.tokens_cache_write + } + + for (const [sessionID, value] of usageBySession) { + db.update(SessionTable) + .set({ + cost: value.cost, + tokens_input: value.tokens.input, + tokens_output: value.tokens.output, + tokens_reasoning: value.tokens.reasoning, + tokens_cache_read: value.tokens.cache.read, + tokens_cache_write: value.tokens.cache.write, + }) + .where(eq(SessionTable.id, sessionID)) + .run() + } + }), + ) + + return sessions.at(-1)?.id + }).pipe( + Effect.withSpan("DataMigration.sessionUsage.page", { + attributes: { + "data_migration.name": "session_usage_from_messages", + "data_migration.page": page, + "data_migration.cursor": cursor ?? "", + }, + }), + ) + if (!next) return + cursor = next + yield* Effect.sleep("10 millis") + } + }), + }, + ] yield* Effect.gen(function* () { if (migrations.length === 0) return diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 93acd4546..8b5cc2bdc 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -1,6 +1,8 @@ import { NotFoundError } from "@/storage/storage" import { eq } from "drizzle-orm" import { and } from "drizzle-orm" +import { sql } from "drizzle-orm" +import type { TxOrDb } from "@/storage/db" import { SyncEvent } from "@/sync" import * as Session from "./session" import { MessageV2 } from "./message-v2" @@ -19,6 +21,28 @@ function foreign(err: unknown) { export type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial | null } : T +type Usage = Pick + +function usage(part: MessageV2.Part | (typeof PartTable.$inferSelect)["data"]): Usage | undefined { + if (part.type !== "step-finish") return undefined + if (!("cost" in part) || !("tokens" in part)) return undefined + return { cost: part.cost, tokens: part.tokens } +} + +function applyUsage(db: TxOrDb, sessionID: Session.Info["id"], value: Usage, sign = 1) { + db.update(SessionTable) + .set({ + cost: sql`${SessionTable.cost} + ${value.cost * sign}`, + tokens_input: sql`${SessionTable.tokens_input} + ${value.tokens.input * sign}`, + tokens_output: sql`${SessionTable.tokens_output} + ${value.tokens.output * sign}`, + tokens_reasoning: sql`${SessionTable.tokens_reasoning} + ${value.tokens.reasoning * sign}`, + tokens_cache_read: sql`${SessionTable.tokens_cache_read} + ${value.tokens.cache.read * sign}`, + tokens_cache_write: sql`${SessionTable.tokens_cache_write} + ${value.tokens.cache.write * sign}`, + }) + .where(eq(SessionTable.id, sessionID)) + .run() +} + function grab( obj: T, field1: K1, @@ -54,6 +78,12 @@ export function toPartialRow(info: DeepPartial) { summary_deletions: grab(info, "summary", (v) => grab(v, "deletions")), summary_files: grab(info, "summary", (v) => grab(v, "files")), summary_diffs: grab(info, "summary", (v) => grab(v, "diffs")), + cost: grab(info, "cost"), + tokens_input: grab(info, "tokens", (v) => grab(v, "input")), + tokens_output: grab(info, "tokens", (v) => grab(v, "output")), + tokens_reasoning: grab(info, "tokens", (v) => grab(v, "reasoning")), + tokens_cache_read: grab(info, "tokens", (v) => grab(v, "cache", (cache) => grab(cache, "read"))), + tokens_cache_write: grab(info, "tokens", (v) => grab(v, "cache", (cache) => grab(cache, "write"))), revert: grab(info, "revert"), permission: grab(info, "permission"), time_created: grab(info, "time", (v) => grab(v, "created")), @@ -112,12 +142,28 @@ export default [ }), SyncEvent.project(MessageV2.Event.Removed, (db, data) => { + for (const row of db + .select() + .from(PartTable) + .where(and(eq(PartTable.message_id, data.messageID), eq(PartTable.session_id, data.sessionID))) + .all()) { + const previous = usage(row.data) + if (previous) applyUsage(db, data.sessionID, previous, -1) + } db.delete(MessageTable) .where(and(eq(MessageTable.id, data.messageID), eq(MessageTable.session_id, data.sessionID))) .run() }), SyncEvent.project(MessageV2.Event.PartRemoved, (db, data) => { + const row = db + .select() + .from(PartTable) + .where(and(eq(PartTable.id, data.partID), eq(PartTable.session_id, data.sessionID))) + .get() + const previous = row && usage(row.data) + if (previous) applyUsage(db, data.sessionID, previous, -1) + db.delete(PartTable) .where(and(eq(PartTable.id, data.partID), eq(PartTable.session_id, data.sessionID))) .run() @@ -125,6 +171,7 @@ export default [ SyncEvent.project(MessageV2.Event.PartUpdated, (db, data) => { const { id, messageID, sessionID, ...rest } = data.part + const row = db.select().from(PartTable).where(eq(PartTable.id, id)).get() try { db.insert(PartTable) @@ -137,6 +184,10 @@ export default [ }) .onConflictDoUpdate({ target: PartTable.id, set: { data: rest } }) .run() + const previous = row && usage(row.data) + const next = usage(data.part) + if (previous) applyUsage(db, row.session_id, previous, -1) + if (next) applyUsage(db, sessionID, next) } catch (err) { if (!foreign(err)) throw err log.warn("ignored late part update", { partID: id, messageID, sessionID }) diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 421fa6869..18d041f45 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -1,4 +1,4 @@ -import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core" +import { sqliteTable, text, integer, index, primaryKey, real } from "drizzle-orm/sqlite-core" import { ProjectTable } from "../project/project.sql" import type { MessageV2 } from "./message-v2" import type { SessionMessage } from "../v2/session-message" @@ -10,7 +10,7 @@ import type { WorkspaceID } from "../control-plane/schema" import { Timestamps } from "../storage/schema.sql" type PartData = Omit -type InfoData = Omit +type InfoData = T extends unknown ? Omit : never type SessionMessageData = Omit<(typeof SessionMessage.Message)["Encoded"], "type" | "id"> export const SessionTable = sqliteTable( @@ -33,6 +33,12 @@ export const SessionTable = sqliteTable( summary_deletions: integer(), summary_files: integer(), summary_diffs: text({ mode: "json" }).$type(), + cost: real().notNull().default(0), + tokens_input: integer().notNull().default(0), + tokens_output: integer().notNull().default(0), + tokens_reasoning: integer().notNull().default(0), + tokens_cache_read: integer().notNull().default(0), + tokens_cache_write: integer().notNull().default(0), revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: PartID; snapshot?: string; diff?: string }>(), permission: text({ mode: "json" }).$type(), agent: text(), diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 92b4329e6..eff027579 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -87,6 +87,16 @@ export function fromRow(row: SessionRow): Info { : undefined, version: row.version, summary, + cost: row.cost, + tokens: { + input: row.tokens_input, + output: row.tokens_output, + reasoning: row.tokens_reasoning, + cache: { + read: row.tokens_cache_read, + write: row.tokens_cache_write, + }, + }, share, revert, permission: row.permission ?? undefined, @@ -117,6 +127,12 @@ export function toRow(info: Info) { summary_deletions: info.summary?.deletions, summary_files: info.summary?.files, summary_diffs: info.summary?.diffs, + cost: info.cost ?? 0, + tokens_input: (info.tokens ?? EmptyTokens).input, + tokens_output: (info.tokens ?? EmptyTokens).output, + tokens_reasoning: (info.tokens ?? EmptyTokens).reasoning, + tokens_cache_read: (info.tokens ?? EmptyTokens).cache.read, + tokens_cache_write: (info.tokens ?? EmptyTokens).cache.write, revert: info.revert ?? null, permission: info.permission, time_created: info.time.created, @@ -147,6 +163,18 @@ const Summary = Schema.Struct({ diffs: optionalOmitUndefined(Schema.Array(Snapshot.FileDiff)), }) +const Tokens = Schema.Struct({ + input: Schema.Finite, + output: Schema.Finite, + reasoning: Schema.Finite, + cache: Schema.Struct({ + read: Schema.Finite, + write: Schema.Finite, + }), +}) + +const EmptyTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } + const Share = Schema.Struct({ url: Schema.String, }) @@ -184,6 +212,8 @@ export const Info = Schema.Struct({ path: optionalOmitUndefined(Schema.String), parentID: optionalOmitUndefined(SessionID), summary: optionalOmitUndefined(Summary), + cost: optionalOmitUndefined(Schema.Finite), + tokens: optionalOmitUndefined(Tokens), share: optionalOmitUndefined(Share), title: Schema.String, agent: optionalOmitUndefined(Schema.String), @@ -281,6 +311,8 @@ const UpdatedInfo = Schema.Struct({ path: Schema.optional(Schema.NullOr(Schema.String)), parentID: Schema.optional(Schema.NullOr(SessionID)), summary: Schema.optional(Schema.NullOr(Summary)), + cost: Schema.optional(Schema.Finite), + tokens: Schema.optional(Tokens), share: Schema.optional(UpdatedShare), title: Schema.optional(Schema.NullOr(Schema.String)), agent: Schema.optional(Schema.NullOr(Schema.String)), @@ -503,6 +535,8 @@ export const layer: Layer.Layer | NodeSQLiteDatabase("Session.Info")({ path: optionalOmitUndefined(Schema.String), agent: optionalOmitUndefined(Schema.String), model: Modelv2.Ref.pipe(optionalOmitUndefined), + cost: Schema.Finite, + tokens: Schema.Struct({ + input: Schema.Finite, + output: Schema.Finite, + reasoning: Schema.Finite, + cache: Schema.Struct({ + read: Schema.Finite, + write: Schema.Finite, + }), + }), time: Schema.Struct({ created: V2Schema.DateTimeUtcFromMillis, updated: V2Schema.DateTimeUtcFromMillis, @@ -136,6 +146,16 @@ export const layer = Layer.effect( variant: Modelv2.VariantID.make(row.model.variant ?? "default"), } : undefined, + cost: row.cost, + tokens: { + input: row.tokens_input, + output: row.tokens_output, + reasoning: row.tokens_reasoning, + cache: { + read: row.tokens_cache_read, + write: row.tokens_cache_write, + }, + }, time: { created: DateTime.makeUnsafe(row.time_created), updated: DateTime.makeUnsafe(row.time_updated), diff --git a/packages/opencode/test/effect/runner.test.ts b/packages/opencode/test/effect/runner.test.ts index 0f5783bfc..c37cb276b 100644 --- a/packages/opencode/test/effect/runner.test.ts +++ b/packages/opencode/test/effect/runner.test.ts @@ -1,5 +1,5 @@ import { describe, expect } from "bun:test" -import { Deferred, Effect, Exit, Fiber, Ref, Scope } from "effect" +import { Deferred, Effect, Exit, Fiber, Latch, Ref, Scope } from "effect" import { Runner } from "@/effect/runner" import { it } from "../lib/effect" @@ -352,11 +352,18 @@ describe("Runner", () => { Effect.gen(function* () { const s = yield* Scope.Scope const runner = Runner.make(s, { onInterrupt: Effect.succeed("interrupted") }) + const ready = yield* Latch.make() const sh = yield* runner - .startShell(Effect.never.pipe(Effect.ensuring(Effect.die("boom")), Effect.as("ignored"))) + .startShell( + Effect.gen(function* () { + yield* ready.open + return yield* Effect.never.pipe(Effect.as("ignored")) + }).pipe(Effect.ensuring(Effect.die("boom"))), + ready, + ) .pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* ready.await.pipe(Effect.timeout("250 millis")) yield* runner.cancel expect(Exit.isFailure(yield* Fiber.await(sh))).toBe(true) diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts index 62a3ae6e6..3d894bd0a 100644 --- a/packages/opencode/test/fixture/tui-plugin.ts +++ b/packages/opencode/test/fixture/tui-plugin.ts @@ -292,6 +292,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { }, session: { count: opts.state?.session?.count ?? (() => 0), + get: opts.state?.session?.get ?? (() => undefined), diff: opts.state?.session?.diff ?? (() => []), todo: opts.state?.session?.todo ?? (() => []), messages: opts.state?.session?.messages ?? (() => []), diff --git a/packages/opencode/test/session/session-schema.test.ts b/packages/opencode/test/session/session-schema.test.ts index 38531d15b..906414fdb 100644 --- a/packages/opencode/test/session/session-schema.test.ts +++ b/packages/opencode/test/session/session-schema.test.ts @@ -12,6 +12,8 @@ const info = { directory: "/tmp/opencode", parentID: undefined, summary: undefined, + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, share: undefined, title: "Test session", version: "1.0.0", diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index 851b0476e..d4c2261b2 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -11,6 +11,7 @@ import type { Provider, PermissionRequest, QuestionRequest, + Session, SessionStatus, TextPart, Config as SdkConfig, @@ -310,6 +311,7 @@ export type TuiState = { readonly vcs: { branch?: string } | undefined session: { count: () => number + get: (sessionID: string) => Session | undefined diff: (sessionID: string) => ReadonlyArray todo: (sessionID: string) => ReadonlyArray messages: (sessionID: string) => ReadonlyArray diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index c7a479f5a..fd5783660 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -741,6 +741,16 @@ export type Session = { files: number diffs?: Array } + cost?: number + tokens?: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } share?: { url: string } @@ -1430,6 +1440,16 @@ export type GlobalSession = { files: number diffs?: Array } + cost?: number + tokens?: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } share?: { url: string } @@ -1893,6 +1913,16 @@ export type SyncEventSessionUpdated = { files: number diffs?: Array } | null + cost?: number | null + tokens?: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } | null share?: { url?: string | null } @@ -3085,6 +3115,16 @@ export type SessionInfo = { providerID: string variant: string } + cost: number + tokens: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } time: { created: number updated: number From ea6eabe1d93f6acbf8ebbcbee95eba61de1bb2cf Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 05:20:22 +0000 Subject: [PATCH 084/378] chore: generate --- .../snapshot.json | 144 +++++------------- packages/opencode/src/data-migration.ts | 5 +- packages/sdk/openapi.json | 144 +++++++++++++++++- 3 files changed, 183 insertions(+), 110 deletions(-) diff --git a/packages/opencode/migration/20260510033149_session_usage/snapshot.json b/packages/opencode/migration/20260510033149_session_usage/snapshot.json index 4ec5dbc52..ce5e56f48 100644 --- a/packages/opencode/migration/20260510033149_session_usage/snapshot.json +++ b/packages/opencode/migration/20260510033149_session_usage/snapshot.json @@ -2,9 +2,7 @@ "version": "7", "dialect": "sqlite", "id": "be5eae31-b7f8-4292-8827-c36a524abd1b", - "prevIds": [ - "630a93f2-c6c6-4191-a351-868d8f3a05d4" - ], + "prevIds": ["630a93f2-c6c6-4191-a351-868d8f3a05d4"], "ddl": [ { "name": "account_state", @@ -1153,13 +1151,9 @@ "table": "event" }, { - "columns": [ - "active_account_id" - ], + "columns": ["active_account_id"], "tableTo": "account", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "SET NULL", "nameExplicit": false, @@ -1168,13 +1162,9 @@ "table": "account_state" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1183,13 +1173,9 @@ "table": "workspace" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1198,13 +1184,9 @@ "table": "message" }, { - "columns": [ - "message_id" - ], + "columns": ["message_id"], "tableTo": "message", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1213,13 +1195,9 @@ "table": "part" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1228,13 +1206,9 @@ "table": "permission" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1243,13 +1217,9 @@ "table": "session_message" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1258,13 +1228,9 @@ "table": "session" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1273,13 +1239,9 @@ "table": "todo" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1288,13 +1250,9 @@ "table": "session_share" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "tableTo": "event_sequence", - "columnsTo": [ - "aggregate_id" - ], + "columnsTo": ["aggregate_id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1303,128 +1261,98 @@ "table": "event" }, { - "columns": [ - "email", - "url" - ], + "columns": ["email", "url"], "nameExplicit": false, "name": "control_account_pk", "entityType": "pks", "table": "control_account" }, { - "columns": [ - "session_id", - "position" - ], + "columns": ["session_id", "position"], "nameExplicit": false, "name": "todo_pk", "entityType": "pks", "table": "todo" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_state_pk", "table": "account_state", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_pk", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "workspace_pk", "table": "workspace", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "project_pk", "table": "project", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "message_pk", "table": "message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "part_pk", "table": "part", "entityType": "pks" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "nameExplicit": false, "name": "permission_pk", "table": "permission", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_message_pk", "table": "session_message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_pk", "table": "session", "entityType": "pks" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "nameExplicit": false, "name": "session_share_pk", "table": "session_share", "entityType": "pks" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "nameExplicit": false, "name": "event_sequence_pk", "table": "event_sequence", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "event_pk", "table": "event", @@ -1588,4 +1516,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/opencode/src/data-migration.ts b/packages/opencode/src/data-migration.ts index f2268b4c8..53e3196b7 100644 --- a/packages/opencode/src/data-migration.ts +++ b/packages/opencode/src/data-migration.ts @@ -66,7 +66,10 @@ export const layer = Layer.effect( .from(MessageTable) .where( and( - inArray(MessageTable.session_id, sessions.map((session) => session.id)), + inArray( + MessageTable.session_id, + sessions.map((session) => session.id), + ), sql`json_extract(${MessageTable.data}, '$.role') = 'assistant'`, ), ) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 3d452cc9c..9850c8341 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11018,6 +11018,38 @@ "required": ["additions", "deletions", "files"], "additionalProperties": false }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"], + "additionalProperties": false + } + }, + "required": ["input", "output", "reasoning", "cache"], + "additionalProperties": false + }, "share": { "type": "object", "properties": { @@ -12891,6 +12923,38 @@ "required": ["additions", "deletions", "files"], "additionalProperties": false }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"], + "additionalProperties": false + } + }, + "required": ["input", "output", "reasoning", "cache"], + "additionalProperties": false + }, "share": { "type": "object", "properties": { @@ -14389,6 +14453,52 @@ } ] }, + "cost": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "tokens": { + "anyOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"], + "additionalProperties": false + } + }, + "required": ["input", "output", "reasoning", "cache"], + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, "share": { "type": "object", "properties": { @@ -18138,6 +18248,38 @@ "required": ["id", "providerID", "variant"], "additionalProperties": false }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": ["read", "write"], + "additionalProperties": false + } + }, + "required": ["input", "output", "reasoning", "cache"], + "additionalProperties": false + }, "time": { "type": "object", "properties": { @@ -18158,7 +18300,7 @@ "type": "string" } }, - "required": ["id", "projectID", "time", "title"], + "required": ["id", "projectID", "cost", "tokens", "time", "title"], "additionalProperties": false }, "SessionDelivery": { From 3992e2a76b3e0d064cc8250d5b6008d7dd5dbb4e Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 12 May 2026 14:43:07 +0800 Subject: [PATCH 085/378] feat(app): add ctrl/cmd+number keybinds to switch projects (#26280) --- .../app/src/components/dialog-select-file.tsx | 2 +- .../app/src/components/settings-keybinds.tsx | 2 ++ packages/app/src/context/command.tsx | 17 ++++++++------ packages/app/src/i18n/en.ts | 1 + packages/app/src/pages/layout.tsx | 22 +++++++++++++++++++ 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index 63a321e46..c5ac52919 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -107,7 +107,7 @@ function createCommandEntries(props: { const allowed = createMemo(() => { if (props.filesOnly()) return [] return props.command.options.filter( - (option) => !option.disabled && !option.id.startsWith("suggested.") && option.id !== "file.open", + (option) => !option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open", ) }) diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index 7d2dfaa63..149a0309b 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -123,11 +123,13 @@ function listFor(command: CommandContext, map: KeybindMap, palette: string) { for (const opt of command.catalog) { if (opt.id.startsWith("suggested.")) continue + if (opt.hidden) continue out.set(opt.id, { title: opt.title, group: groupFor(opt.id) }) } for (const opt of command.options) { if (opt.id.startsWith("suggested.")) continue + if (opt.hidden) continue out.set(opt.id, { title: opt.title, group: groupFor(opt.id) }) } diff --git a/packages/app/src/context/command.tsx b/packages/app/src/context/command.tsx index d2238828c..e979ad6a0 100644 --- a/packages/app/src/context/command.tsx +++ b/packages/app/src/context/command.tsx @@ -81,6 +81,7 @@ export interface CommandOption { slash?: string suggested?: boolean disabled?: boolean + hidden?: boolean onSelect?: (source?: "palette" | "keybind" | "slash") => void onHighlight?: () => (() => void) | void } @@ -93,6 +94,7 @@ export type CommandCatalogItem = { category?: string keybind?: KeybindConfig slash?: string + hidden?: boolean } export type CommandRegistration = { @@ -279,13 +281,14 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex setCatalog( registered().reduce((acc, opt) => { const id = actionId(opt.id) - acc[id] = { - title: opt.title, - description: opt.description, - category: opt.category, - keybind: opt.keybind, - slash: opt.slash, - } + if (opt.title) + acc[id] = { + title: opt.title, + description: opt.description, + category: opt.category, + keybind: opt.keybind, + slash: opt.slash, + } return acc }, {} as CommandCatalog), ) diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 250d26edb..a42bb6261 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -25,6 +25,7 @@ export const dict = { "command.project.open": "Open project", "command.project.previous": "Previous project", "command.project.next": "Next project", + "command.project.index": "Switch to project {{index}}", "command.provider.connect": "Connect provider", "command.server.switch": "Switch server", "command.settings.open": "Open settings", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 11bc4fdb5..31d3e5dcc 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -960,6 +960,15 @@ export default function Layout(props: ParentProps) { void openProject(target.worktree) } + function navigateToProjectIndex(index: number) { + const projects = layout.projects.list() + const target = projects[index] + if (!target) return + + globalSync.child(target.worktree) + void openProject(target.worktree) + } + function navigateSessionByUnseen(offset: number) { const sessions = currentSessions() if (sessions.length === 0) return @@ -1040,6 +1049,19 @@ export default function Layout(props: ParentProps) { keybind: "mod+alt+arrowdown", onSelect: () => navigateProjectByOffset(1), }, + ...Array.from({ length: 9 }, (_, i) => { + const index = i + const number = index + 1 + return { + id: `project.${number}`, + category: language.t("command.category.project"), + title: `Open Project {number}`, + keybind: `mod+${number}`, + disabled: layout.projects.list().length <= index, + hidden: true, + onSelect: () => navigateToProjectIndex(index), + } + }), { id: "provider.connect", title: language.t("command.provider.connect"), From 907281a80a61d57f06354a74ff1c8195e0778c76 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 06:44:09 +0000 Subject: [PATCH 086/378] chore: generate --- packages/app/src/components/dialog-select-file.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index c5ac52919..ac3bc03e4 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -107,7 +107,8 @@ function createCommandEntries(props: { const allowed = createMemo(() => { if (props.filesOnly()) return [] return props.command.options.filter( - (option) => !option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open", + (option) => + !option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open", ) }) From 61174b787855a582f5adcce484b0d2cde4e2682b Mon Sep 17 00:00:00 2001 From: Matt H Date: Tue, 12 May 2026 04:01:16 -0400 Subject: [PATCH 087/378] fix(tui): make websearch provider label reactive (#26943) --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 3e966d9a5..95d1b072f 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1985,11 +1985,11 @@ function WebFetch(props: ToolProps) { } function WebSearch(props: ToolProps) { - const metadata = props.metadata as { numResults?: number; provider?: unknown } + const metadata = () => props.metadata as { numResults?: number; provider?: unknown } return ( - {webSearchProviderLabel(metadata.provider)} "{props.input.query}"{" "} - ({metadata.numResults} results) + {webSearchProviderLabel(metadata().provider)} "{props.input.query}"{" "} + ({metadata().numResults} results) ) } From 2481dde36d9aeb48de9fb21ffd2cfdc1d42804f1 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 12 May 2026 13:44:02 +0530 Subject: [PATCH 088/378] chore: remove codesearch tool (#27019) --- packages/opencode/src/agent/agent.ts | 1 - .../tui/feature-plugins/system/session-v2.tsx | 12 ---- packages/opencode/src/config/permission.ts | 1 - .../src/skill/prompt/customize-opencode.md | 6 +- packages/opencode/src/tool/codesearch.ts | 63 ------------------- packages/opencode/src/tool/codesearch.txt | 12 ---- packages/opencode/src/tool/mcp-websearch.ts | 5 -- packages/opencode/src/tool/registry.ts | 5 +- packages/opencode/test/tool/registry.test.ts | 2 - packages/sdk/js/src/v2/gen/types.gen.ts | 1 - packages/sdk/openapi.json | 3 - 11 files changed, 4 insertions(+), 107 deletions(-) delete mode 100644 packages/opencode/src/tool/codesearch.ts delete mode 100644 packages/opencode/src/tool/codesearch.txt diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index b9b56396f..c1a644282 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -206,7 +206,6 @@ export const layer = Layer.effect( glob: "allow", webfetch: "allow", websearch: "allow", - codesearch: "allow", read: "allow", repo_clone: "allow", repo_overview: "allow", diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx index 8b741ccb4..bcf3032ea 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx @@ -438,9 +438,6 @@ function AssistantTool(props: { part: SessionMessageAssistantTool; sessionID: st - - - @@ -773,15 +770,6 @@ function WebFetch(props: ToolProps) { ) } -function CodeSearch(props: ToolProps) { - return ( - - Exa Code Search "{stringValue(props.input.query) ?? pendingInput(props.part)}"{" "} - {(results) => <>({results()} results)} - - ) -} - function WebSearch(props: ToolProps) { const label = createMemo(() => webSearchProviderLabel(props.metadata.provider)) return ( diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts index a04b404e8..1092ae2b7 100644 --- a/packages/opencode/src/config/permission.ts +++ b/packages/opencode/src/config/permission.ts @@ -27,7 +27,6 @@ const InputObject = Schema.StructWithRest( question: Schema.optional(Action), webfetch: Schema.optional(Action), websearch: Schema.optional(Action), - codesearch: Schema.optional(Action), repo_clone: Schema.optional(Rule), repo_overview: Schema.optional(Rule), lsp: Schema.optional(Rule), diff --git a/packages/opencode/src/skill/prompt/customize-opencode.md b/packages/opencode/src/skill/prompt/customize-opencode.md index 744690b15..4ba118b09 100644 --- a/packages/opencode/src/skill/prompt/customize-opencode.md +++ b/packages/opencode/src/skill/prompt/customize-opencode.md @@ -335,9 +335,9 @@ rules last. everything" and is rarely what the user wants. Known permission keys: `read, edit, glob, grep, list, bash, task, -external_directory, todowrite, question, webfetch, websearch, codesearch, -repo_clone, repo_overview, lsp, doom_loop, skill`. Some of these (`todowrite, -question, webfetch, websearch, codesearch, doom_loop`) only accept a flat +external_directory, todowrite, question, webfetch, websearch, repo_clone, +repo_overview, lsp, doom_loop, skill`. Some of these (`todowrite, +question, webfetch, websearch, doom_loop`) only accept a flat action, not a per-pattern object. `external_directory` patterns are filesystem paths (use `~/`, absolute paths, diff --git a/packages/opencode/src/tool/codesearch.ts b/packages/opencode/src/tool/codesearch.ts deleted file mode 100644 index 4616d5900..000000000 --- a/packages/opencode/src/tool/codesearch.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Effect, Schema } from "effect" -import { HttpClient } from "effect/unstable/http" -import * as Tool from "./tool" -import * as McpWebSearch from "./mcp-websearch" -import DESCRIPTION from "./codesearch.txt" - -export const Parameters = Schema.Struct({ - query: Schema.String.annotate({ - description: - "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'", - }), - tokensNum: Schema.Number.check(Schema.isGreaterThanOrEqualTo(1000)) - .check(Schema.isLessThanOrEqualTo(50000)) - .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(5000))) - .annotate({ - description: - "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.", - }), -}) - -export const CodeSearchTool = Tool.define( - "codesearch", - Effect.gen(function* () { - const http = yield* HttpClient.HttpClient - - return { - description: DESCRIPTION, - parameters: Parameters, - execute: (params: { query: string; tokensNum: number }, ctx: Tool.Context) => - Effect.gen(function* () { - yield* ctx.ask({ - permission: "codesearch", - patterns: [params.query], - always: ["*"], - metadata: { - query: params.query, - tokensNum: params.tokensNum, - }, - }) - - const result = yield* McpWebSearch.call( - http, - McpWebSearch.EXA_URL, - "get_code_context_exa", - McpWebSearch.CodeArgs, - { - query: params.query, - tokensNum: params.tokensNum, - }, - "30 seconds", - ) - - return { - output: - result ?? - "No code snippets or documentation found. Please try a different query, be more specific about the library or programming concept, or check the spelling of framework names.", - title: `Code search: ${params.query}`, - metadata: {}, - } - }).pipe(Effect.orDie), - } - }), -) diff --git a/packages/opencode/src/tool/codesearch.txt b/packages/opencode/src/tool/codesearch.txt deleted file mode 100644 index 4187f08d1..000000000 --- a/packages/opencode/src/tool/codesearch.txt +++ /dev/null @@ -1,12 +0,0 @@ -- Search and get relevant context for any programming task using Exa Code API -- Provides the highest quality and freshest context for libraries, SDKs, and APIs -- Use this tool for ANY question or task related to programming -- Returns comprehensive code examples, documentation, and API references -- Optimized for finding specific programming patterns and solutions - -Usage notes: - - Adjustable token count (1000-50000) for focused or comprehensive results - - Default 5000 tokens provides balanced context for most queries - - Use lower values for specific questions, higher values for comprehensive documentation - - Supports queries about frameworks, libraries, APIs, and programming concepts - - Examples: 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware' diff --git a/packages/opencode/src/tool/mcp-websearch.ts b/packages/opencode/src/tool/mcp-websearch.ts index 42b864c6f..208924cba 100644 --- a/packages/opencode/src/tool/mcp-websearch.ts +++ b/packages/opencode/src/tool/mcp-websearch.ts @@ -48,11 +48,6 @@ export const SearchArgs = Schema.Struct({ contextMaxCharacters: Schema.optional(Schema.Number), }) -export const CodeArgs = Schema.Struct({ - query: Schema.String, - tokensNum: Schema.Number, -}) - export const ParallelSearchArgs = Schema.Struct({ objective: Schema.String, search_queries: Schema.Array(Schema.String), diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index a7411a077..f72f10dd1 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -22,7 +22,6 @@ import { Plugin } from "../plugin" import { Provider } from "@/provider/provider" import { ProviderID, type ModelID } from "../provider/schema" import { WebSearchTool } from "./websearch" -import { CodeSearchTool } from "./codesearch" import { RepoCloneTool } from "./repo_clone" import { RepoOverviewTool } from "./repo_overview" import { Flag } from "@opencode-ai/core/flag/flag" @@ -120,7 +119,6 @@ export const layer: Layer.Layer< const plan = yield* PlanExitTool const webfetch = yield* WebFetchTool const websearch = yield* WebSearchTool - const codesearch = yield* CodeSearchTool const repoClone = yield* RepoCloneTool const repoOverview = yield* RepoOverviewTool const shell = yield* ShellTool @@ -224,7 +222,6 @@ export const layer: Layer.Layer< fetch: Tool.init(webfetch), todo: Tool.init(todo), search: Tool.init(websearch), - code: Tool.init(codesearch), repo_clone: Tool.init(repoClone), repo_overview: Tool.init(repoOverview), skill: Tool.init(skilltool), @@ -249,7 +246,7 @@ export const layer: Layer.Layer< tool.fetch, tool.todo, tool.search, - ...(Flag.OPENCODE_EXPERIMENTAL_SCOUT ? [tool.code, tool.repo_clone, tool.repo_overview] : []), + ...(Flag.OPENCODE_EXPERIMENTAL_SCOUT ? [tool.repo_clone, tool.repo_overview] : []), tool.skill, tool.patch, ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []), diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 37cb7a43d..5ee56300c 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -72,7 +72,6 @@ describe("tool.registry", () => { const registry = yield* ToolRegistry.Service const ids = yield* registry.ids() - expect(ids).not.toContain("codesearch") expect(ids).not.toContain("repo_clone") expect(ids).not.toContain("repo_overview") }), @@ -84,7 +83,6 @@ describe("tool.registry", () => { const registry = yield* ToolRegistry.Service const ids = yield* registry.ids() - expect(ids).toContain("codesearch") expect(ids).toContain("repo_clone") expect(ids).toContain("repo_overview") }), diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index fd5783660..f062700b7 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -955,7 +955,6 @@ export type PermissionConfig = question?: PermissionActionConfig webfetch?: PermissionActionConfig websearch?: PermissionActionConfig - codesearch?: PermissionActionConfig repo_clone?: PermissionRuleConfig repo_overview?: PermissionRuleConfig lsp?: PermissionRuleConfig diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 9850c8341..7005382f6 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11631,9 +11631,6 @@ "websearch": { "$ref": "#/components/schemas/PermissionActionConfig" }, - "codesearch": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, "repo_clone": { "$ref": "#/components/schemas/PermissionRuleConfig" }, From ff38bbeeeb64c7f2faaae54430b5bfa3a2f5435f Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 12 May 2026 16:39:56 +0800 Subject: [PATCH 089/378] refactor(desktop): remove configureEnv callback from spawnLocalServer (#27022) --- packages/desktop/src/main/index.ts | 28 +++++++++++----------------- packages/desktop/src/main/server.ts | 2 -- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/desktop/src/main/index.ts b/packages/desktop/src/main/index.ts index 1b624800e..23f2d7027 100644 --- a/packages/desktop/src/main/index.ts +++ b/packages/desktop/src/main/index.ts @@ -291,25 +291,19 @@ const main = Effect.gen(function* () { if (mainWindow) sendSqliteMigrationProgress(mainWindow, progress) }) + ensureLoopbackNoProxy() + useEnvProxy() + logger.log("spawning sidecar", { url }) const { listener, health } = yield* Effect.promise(() => - spawnLocalServer( - hostname, - port, - password, - () => { - ensureLoopbackNoProxy() - useEnvProxy() - }, - { - needsMigration, - userDataPath: app.getPath("userData"), - onSqliteProgress: (progress) => initEmitter.emit("sqlite", progress), - onStdout: (message) => logger.log("sidecar stdout", { message }), - onStderr: (message) => logger.warn("sidecar stderr", { message }), - onExit: (code) => logger.warn("sidecar exited", { code }), - }, - ), + spawnLocalServer(hostname, port, password, { + needsMigration, + userDataPath: app.getPath("userData"), + onSqliteProgress: (progress) => initEmitter.emit("sqlite", progress), + onStdout: (message) => logger.log("sidecar stdout", { message }), + onStderr: (message) => logger.warn("sidecar stderr", { message }), + onExit: (code) => logger.warn("sidecar exited", { code }), + }), ) server = listener yield* Deferred.succeed(serverReady, { diff --git a/packages/desktop/src/main/server.ts b/packages/desktop/src/main/server.ts index 909138b89..cfdafdc67 100644 --- a/packages/desktop/src/main/server.ts +++ b/packages/desktop/src/main/server.ts @@ -70,10 +70,8 @@ export async function spawnLocalServer( hostname: string, port: number, password: string, - configureEnv: () => void, options: SpawnLocalServerOptions, ) { - configureEnv?.() const sidecar = join(dirname(fileURLToPath(import.meta.url)), "sidecar.js") const child = utilityProcess.fork(sidecar, [], { cwd: process.cwd(), From caf1151cb5d574d2aac2ed6ccb20a9121880c18a Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Tue, 12 May 2026 18:40:21 +1000 Subject: [PATCH 090/378] refactor(app): centralize sync query options (#25941) Co-authored-by: Brendan Allan Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> --- .../app/src/components/dialog-select-mcp.tsx | 6 +- packages/app/src/components/prompt-input.tsx | 12 ++-- .../src/components/status-popover-body.tsx | 6 +- packages/app/src/context/global-sync.tsx | 71 +++++++++++-------- .../context/global-sync/child-store.test.ts | 2 +- .../src/context/global-sync/child-store.ts | 17 ++--- .../src/pages/layout/sidebar-workspace.tsx | 8 ++- 7 files changed, 70 insertions(+), 52 deletions(-) diff --git a/packages/app/src/components/dialog-select-mcp.tsx b/packages/app/src/components/dialog-select-mcp.tsx index 576ec8fec..cc841e278 100644 --- a/packages/app/src/components/dialog-select-mcp.tsx +++ b/packages/app/src/components/dialog-select-mcp.tsx @@ -6,7 +6,8 @@ import { Dialog } from "@opencode-ai/ui/dialog" import { List } from "@opencode-ai/ui/list" import { Switch } from "@opencode-ai/ui/switch" import { useLanguage } from "@/context/language" -import { mcpQueryKey } from "@/context/global-sync" +import { useQueryOptions } from "@/context/global-sync" +import { pathKey } from "@/utils/path-key" const statusLabels = { connected: "mcp.status.connected", @@ -20,6 +21,7 @@ export const DialogSelectMcp: Component = () => { const sdk = useSDK() const language = useLanguage() const queryClient = useQueryClient() + const queryOptions = useQueryOptions() const items = createMemo(() => Object.entries(sync.data.mcp ?? {}) @@ -32,7 +34,7 @@ export const DialogSelectMcp: Component = () => { if (sync.data.mcp[name]?.status === "connected") await sdk.client.mcp.disconnect({ name }) else await sdk.client.mcp.connect({ name }) }, - onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }), + onSuccess: () => queryClient.refetchQueries(queryOptions.mcp(pathKey(sync.directory))), })) const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 2417fa98e..eaeedf087 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -16,7 +16,6 @@ import { } from "@/context/prompt" import { useLayout } from "@/context/layout" import { useSDK } from "@/context/sdk" -import { useGlobalSDK } from "@/context/global-sdk" import { useSync } from "@/context/sync" import { useComments } from "@/context/comments" import { Button } from "@opencode-ai/ui/button" @@ -56,7 +55,8 @@ import { PromptDragOverlay } from "./prompt-input/drag-overlay" import { promptPlaceholder } from "./prompt-input/placeholder" import { ImagePreview } from "@opencode-ai/ui/image-preview" import { useQueries } from "@tanstack/solid-query" -import { loadAgentsQuery, loadProvidersQuery } from "@/context/global-sync/bootstrap" +import { useQueryOptions } from "@/context/global-sync" +import { pathKey } from "@/utils/path-key" interface PromptInputProps { class?: string @@ -103,7 +103,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/ export const PromptInput: Component = (props) => { const sdk = useSDK() - const globalSDK = useGlobalSDK() + const queryOptions = useQueryOptions() const sync = useSync() const local = useLocal() @@ -1256,9 +1256,9 @@ export const PromptInput: Component = (props) => { const [agentsQuery, globalProvidersQuery, providersQuery] = useQueries(() => ({ queries: [ - loadAgentsQuery(sdk.directory, sdk.client), - loadProvidersQuery(null, globalSDK.client), - loadProvidersQuery(sdk.directory, sdk.client), + queryOptions.agents(pathKey(sdk.directory)), + queryOptions.providers(null), + queryOptions.providers(pathKey(sdk.directory)), ], })) diff --git a/packages/app/src/components/status-popover-body.tsx b/packages/app/src/components/status-popover-body.tsx index bbac56278..405c7538c 100644 --- a/packages/app/src/components/status-popover-body.tsx +++ b/packages/app/src/components/status-popover-body.tsx @@ -15,7 +15,8 @@ import { useSDK } from "@/context/sdk" import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server" import { useSync } from "@/context/sync" import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health" -import { mcpQueryKey } from "@/context/global-sync" +import { useQueryOptions } from "@/context/global-sync" +import { pathKey } from "@/utils/path-key" const pollMs = 10_000 @@ -139,13 +140,14 @@ const useMcpToggleMutation = () => { const sdk = useSDK() const language = useLanguage() const queryClient = useQueryClient() + const queryOptions = useQueryOptions() return useMutation(() => ({ mutationFn: async (name: string) => { const status = sync.data.mcp[name] await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name })) }, - onSuccess: () => queryClient.refetchQueries({ queryKey: mcpQueryKey(sync.directory) }), + onSuccess: () => queryClient.refetchQueries(queryOptions.mcp(pathKey(sync.directory))), onError: (err) => { showToast({ variant: "error", diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 31c90463d..594f94fb6 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -18,8 +18,10 @@ import { bootstrapDirectory, bootstrapGlobal, clearProviderRev, + loadAgentsQuery, loadGlobalConfigQuery, loadPathQuery, + loadProjectsQuery, loadProvidersQuery, } from "./global-sync/bootstrap" import { createChildStoreManager } from "./global-sync/child-store" @@ -33,6 +35,7 @@ import { formatServerError } from "@/utils/server-errors" import { queryOptions, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query" import { createRefreshQueue } from "./global-sync/queue" import { directoryKey } from "./global-sync/utils" +import { PathKey } from "@/utils/path-key" type GlobalStore = { ready: boolean @@ -48,24 +51,33 @@ type GlobalStore = { reload: undefined | "pending" | "complete" } -export const loadSessionsQueryKey = (directory: string) => [directory, "loadSessions"] as const - -export const mcpQueryKey = (directory: string) => [directory, "mcp"] as const - export const loadMcpQuery = (directory: string, sdk: OpencodeClient) => queryOptions({ - queryKey: mcpQueryKey(directory), + queryKey: [directory, "mcp"] as const, queryFn: () => sdk.mcp.status().then((r) => r.data ?? {}), }) -export const lspQueryKey = (directory: string) => [directory, "lsp"] as const - export const loadLspQuery = (directory: string, sdk: OpencodeClient) => queryOptions({ - queryKey: lspQueryKey(directory), + queryKey: [directory, "lsp"] as const, queryFn: () => sdk.lsp.status().then((r) => r.data ?? []), }) +function makeQueryOptionsApi(globalSDK: () => OpencodeClient, sdkFor: (dir: PathKey) => OpencodeClient) { + return { + globalConfig: () => loadGlobalConfigQuery(globalSDK()), + projects: () => loadProjectsQuery(globalSDK()), + providers: (directory: PathKey | null) => + loadProvidersQuery(directory, directory === null ? globalSDK() : sdkFor(directory)), + path: (directory: PathKey | null) => loadPathQuery(directory, directory === null ? globalSDK() : sdkFor(directory)), + agents: (directory: PathKey) => loadAgentsQuery(directory, sdkFor(directory)), + mcp: (directory: PathKey) => loadMcpQuery(directory, sdkFor(directory)), + lsp: (directory: PathKey) => loadLspQuery(directory, sdkFor(directory)), + sessions: (directory: PathKey) => ({ queryKey: [directory, "loadSessions"] as const }), + } +} +export type QueryOptionsApi = ReturnType + function createGlobalSync() { const globalSDK = useGlobalSDK() const language = useLanguage() @@ -77,12 +89,22 @@ function createGlobalSync() { const sessionLoads = new Map>() const sessionMeta = new Map() + const sdkFor = (directory: string) => { + const key = directoryKey(directory) + const cached = sdkCache.get(key) + if (cached) return cached + const sdk = globalSDK.createClient({ + directory, + throwOnError: true, + }) + sdkCache.set(key, sdk) + return sdk + } + + const queryOptionsApi = makeQueryOptionsApi(() => globalSDK.client, sdkFor) + const [configQuery, providerQuery, pathQuery] = useQueries(() => ({ - queries: [ - loadGlobalConfigQuery(globalSDK.client), - loadProvidersQuery(null, globalSDK.client), - loadPathQuery(null, globalSDK.client), - ], + queries: [queryOptionsApi.globalConfig(), queryOptionsApi.providers(null), queryOptionsApi.path(null)], })) const [globalStore, setGlobalStore] = createStore({ @@ -181,18 +203,6 @@ function createGlobalSync() { bootstrapInstance, }) - const sdkFor = (directory: string) => { - const key = directoryKey(directory) - const cached = sdkCache.get(key) - if (cached) return cached - const sdk = globalSDK.createClient({ - directory, - throwOnError: true, - }) - sdkCache.set(key, sdk) - return sdk - } - const children = createChildStoreManager({ owner, isBooting: (directory) => booting.has(directory), @@ -209,7 +219,7 @@ function createGlobalSync() { clearSessionPrefetchDirectory(key) }, translate: language.t, - getSdk: sdkFor, + queryOptions: queryOptionsApi, global: { provider: globalStore.provider, }, @@ -239,7 +249,7 @@ function createGlobalSync() { const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT) const promise = queryClient .fetchQuery({ - queryKey: loadSessionsQueryKey(key), + ...queryOptionsApi.sessions(key), queryFn: () => loadRootSessionsWithFallback({ directory, @@ -368,7 +378,7 @@ function createGlobalSync() { setSessionTodo, vcsCache: children.vcsCache.get(key), loadLsp: () => { - void queryClient.fetchQuery(loadLspQuery(key, sdkFor(directory))) + void queryClient.fetchQuery(queryOptionsApi.lsp(key)) }, }) }) @@ -426,6 +436,7 @@ function createGlobalSync() { }, child: children.child, peek: children.peek, + queryOptions: queryOptionsApi, // bootstrap, updateConfig: updateConfigMutation.mutateAsync, project: projectApi, @@ -447,3 +458,7 @@ export function useGlobalSync() { if (!context) throw new Error("useGlobalSync must be used within GlobalSyncProvider") return context } + +export function useQueryOptions() { + return useGlobalSync().queryOptions +} diff --git a/packages/app/src/context/global-sync/child-store.test.ts b/packages/app/src/context/global-sync/child-store.test.ts index 30dda8691..bb8eb7ce7 100644 --- a/packages/app/src/context/global-sync/child-store.test.ts +++ b/packages/app/src/context/global-sync/child-store.test.ts @@ -22,7 +22,7 @@ describe("createChildStoreManager", () => { onBootstrap() {}, onDispose() {}, translate: (key) => key, - getSdk: () => null!, + queryOptions: {} as any, global: { provider: null! }, }) diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index 737c6bedc..e8ca597d1 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -1,7 +1,7 @@ import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js" import { createStore, type SetStoreFunction, type Store } from "solid-js/store" import { Persist, persisted } from "@/utils/persist" -import type { OpencodeClient, ProviderListResponse, VcsInfo } from "@opencode-ai/sdk/v2/client" +import type { ProviderListResponse, VcsInfo } from "@opencode-ai/sdk/v2/client" import { DIR_IDLE_TTL_MS, MAX_DIR_STORES, @@ -15,8 +15,7 @@ import { } from "./types" import { canDisposeDirectory, pickDirectoriesToEvict } from "./eviction" import { useQueries } from "@tanstack/solid-query" -import { loadPathQuery, loadProvidersQuery } from "./bootstrap" -import { loadLspQuery, loadMcpQuery } from "../global-sync" +import { QueryOptionsApi } from "../global-sync" import { directoryKey, type DirectoryKey } from "./utils" export function createChildStoreManager(input: { @@ -26,7 +25,7 @@ export function createChildStoreManager(input: { onBootstrap: (directory: string) => void onDispose: (directory: string) => void translate: (key: string, vars?: Record) => string - getSdk: (directory: string) => OpencodeClient + queryOptions: QueryOptionsApi global: { provider: ProviderListResponse } @@ -171,17 +170,15 @@ export function createChildStoreManager(input: { const init = () => createRoot((dispose) => { - const sdk = input.getSdk(directory) - const initialMeta = meta[0].value const initialIcon = icon[0].value const [pathQuery, mcpQuery, lspQuery, providerQuery] = useQueries(() => ({ queries: [ - loadPathQuery(key, sdk), - loadMcpQuery(key, sdk), - loadLspQuery(key, sdk), - loadProvidersQuery(key, sdk), + input.queryOptions.path(key), + input.queryOptions.mcp(key), + input.queryOptions.lsp(key), + input.queryOptions.providers(key), ], })) diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index 9b80adac2..f423c13d1 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -14,7 +14,7 @@ import { Spinner } from "@opencode-ai/ui/spinner" import { Tooltip } from "@opencode-ai/ui/tooltip" import { type Session } from "@opencode-ai/sdk/v2/client" import { type LocalProject } from "@/context/layout" -import { loadSessionsQueryKey, useGlobalSync } from "@/context/global-sync" +import { useGlobalSync, useQueryOptions } from "@/context/global-sync" import { useLanguage } from "@/context/language" import { pathKey } from "@/utils/path-key" import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items" @@ -300,6 +300,7 @@ export const SortableWorkspace = (props: { const navigate = useNavigate() const params = useParams() const globalSync = useGlobalSync() + const queryOptions = useQueryOptions() const language = useLanguage() const sortable = createSortable(props.directory) const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory, { bootstrap: false }) @@ -320,7 +321,7 @@ export const SortableWorkspace = (props: { const boot = createMemo(() => open() || active()) const count = createMemo(() => sessions()?.length ?? 0) const hasMore = createMemo(() => workspaceStore.sessionTotal > count()) - const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.directory) })) + const fetching = useIsFetching(() => queryOptions.sessions(pathKey(props.directory))) const busy = createMemo(() => props.ctx.isBusy(props.directory)) const loading = () => fetching() > 0 && count() === 0 const touch = createMediaQuery("(hover: none)") @@ -446,6 +447,7 @@ export const LocalWorkspace = (props: { mobile?: boolean }): JSX.Element => { const globalSync = useGlobalSync() + const queryOptions = useQueryOptions() const language = useLanguage() const workspace = createMemo(() => { const [store, setStore] = globalSync.child(props.project.worktree) @@ -454,7 +456,7 @@ export const LocalWorkspace = (props: { const slug = createMemo(() => base64Encode(props.project.worktree)) const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow())) const count = createMemo(() => sessions()?.length ?? 0) - const fetching = useIsFetching(() => ({ queryKey: loadSessionsQueryKey(props.project.worktree) })) + const fetching = useIsFetching(() => queryOptions.sessions(pathKey(props.project.worktree))) const hasMore = createMemo(() => workspace().store.sessionTotal > count()) const loading = () => fetching() > 0 && count() === 0 const loadMore = async () => { From d276d96cdfc03dc64cef820c5751d723915d9476 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 12 May 2026 17:44:50 +0800 Subject: [PATCH 091/378] fix(app): remember selected model variant when switching sessions/projects (#27029) --- packages/app/src/context/local.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx index f467e9034..4465a0261 100644 --- a/packages/app/src/context/local.tsx +++ b/packages/app/src/context/local.tsx @@ -44,7 +44,7 @@ const migrate = (value: unknown) => { } const clone = (value: State | undefined) => { - if (!value) return undefined + if (!value) return return { ...value, model: value.model ? { ...value.model } : undefined, @@ -104,7 +104,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const pickAgent = (name: string | undefined) => { const items = list() - if (items.length === 0) return undefined + if (items.length === 0) return return items.find((item) => item.name === name) ?? items[0] } @@ -227,14 +227,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ () => agent.current()?.model, fallback, ) - if (!item) return undefined + if (!item) return return models.find(item) } const configured = () => { const item = agent.current() const model = current() - if (!item || !model) return undefined + if (!item || !model) return return getConfiguredAgentVariant({ agent: { model: item.model, variant: item.variant }, model: { providerID: model.provider.id, modelID: model.id, variants: model.variants }, @@ -314,11 +314,16 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ configured, selected, current() { - return resolveModelVariant({ + const resolved = resolveModelVariant({ variants: this.list(), selected: this.selected(), configured: this.configured(), }) + if (resolved) return resolved + const model = current() + if (!model) return + const saved = models.variant.get({ providerID: model.provider.id, modelID: model.id }) + if (saved && this.list().includes(saved)) return saved }, list() { const item = current() @@ -335,6 +340,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ variant: value ?? null, }) write({ variant: value ?? null }) + if (model) { + models.variant.set({ providerID: model.provider.id, modelID: model.id }, value ?? undefined) + } }) }, cycle() { From 8f05bbfaa62192b357d460e1d2d2b34f13f8dec7 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Tue, 12 May 2026 11:45:28 +0200 Subject: [PATCH 092/378] prompt: fix cursor math for wide characters (#27017) String.length counts code points, not display columns, so CJK characters and emoji that occupy two terminal cells caused misaligned cursors, broken mention triggers, and incorrect history restoration offsets. Use Bun.stringWidth for now, we need an alternative for this. Fix #26716 Close #26922 --- .../opencode/src/cli/cmd/prompt-display.ts | 39 +++++++++++++++ .../src/cli/cmd/run/footer.prompt.tsx | 34 ++++++------- .../opencode/src/cli/cmd/run/prompt.shared.ts | 7 +-- .../cmd/tui/component/prompt/autocomplete.tsx | 12 ++--- .../test/cli/run/prompt.shared.test.ts | 50 +++++++++++++++++++ 5 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/prompt-display.ts diff --git a/packages/opencode/src/cli/cmd/prompt-display.ts b/packages/opencode/src/cli/cmd/prompt-display.ts new file mode 100644 index 000000000..7ec4bc0af --- /dev/null +++ b/packages/opencode/src/cli/cmd/prompt-display.ts @@ -0,0 +1,39 @@ +const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" }) + +function displayOffsetIndex(value: string, offset: number) { + if (offset <= 0) return 0 + + let width = 0 + for (const part of graphemes.segment(value)) { + const next = width + Bun.stringWidth(part.segment) + if (next > offset) return part.index + width = next + } + + return value.length +} + +export function displaySlice(value: string, start = 0, end = Bun.stringWidth(value)) { + return value.slice(displayOffsetIndex(value, start), displayOffsetIndex(value, end)) +} + +export function displayCharAt(value: string, offset: number) { + let width = 0 + for (const part of graphemes.segment(value)) { + const next = width + Bun.stringWidth(part.segment) + if (offset === width || offset < next) return part.segment + width = next + } +} + +export function mentionTriggerIndex(value: string, offset = Bun.stringWidth(value)) { + const text = displaySlice(value, 0, offset) + const index = text.lastIndexOf("@") + if (index === -1) return + + const before = index === 0 ? undefined : text[index - 1] + const query = text.slice(index) + if ((before === undefined || /\s/.test(before)) && !/\s/.test(query)) { + return Bun.stringWidth(text.slice(0, index)) + } +} diff --git a/packages/opencode/src/cli/cmd/run/footer.prompt.tsx b/packages/opencode/src/cli/cmd/run/footer.prompt.tsx index 8cd4fbfcf..54f20dbc0 100644 --- a/packages/opencode/src/cli/cmd/run/footer.prompt.tsx +++ b/packages/opencode/src/cli/cmd/run/footer.prompt.tsx @@ -14,7 +14,10 @@ import { createEffect, createMemo, createResource, createSignal, onCleanup, onMo import * as Locale from "@/util/locale" import { createPromptHistory, + displayCharAt, + displaySlice, isExitCommand, + mentionTriggerIndex, isNewCommand, movePromptHistory, promptCycle, @@ -537,7 +540,7 @@ export function createPromptState(input: PromptInput): PromptState { }) } - const restore = (value: RunPrompt, cursor = value.text.length) => { + const restore = (value: RunPrompt, cursor = Bun.stringWidth(value.text)) => { draft = clonePrompt(value) if (!area || area.isDestroyed) { return @@ -546,7 +549,7 @@ export function createPromptState(input: PromptInput): PromptState { hide() area.setText(value.text) restoreParts(value.parts) - area.cursorOffset = Math.min(cursor, area.plainText.length) + area.cursorOffset = Math.min(cursor, Bun.stringWidth(area.plainText)) scheduleRows() area.focus() } @@ -577,7 +580,7 @@ export function createPromptState(input: PromptInput): PromptState { area.setText(text) clearParts() draft = { text: area.plainText, parts: [] } - area.cursorOffset = Math.min(text.length, area.plainText.length) + area.cursorOffset = Math.min(Bun.stringWidth(text), Bun.stringWidth(area.plainText)) scheduleRows() area.focus() } @@ -610,12 +613,13 @@ export function createPromptState(input: PromptInput): PromptState { } if (visible() && mode() === "mention") { - if (cursor <= at() || /\s/.test(text.slice(at(), cursor))) { + const query = displaySlice(text, at(), cursor) + if (cursor <= at() || /\s/.test(query)) { hide() return } - setQuery(text.slice(at() + 1, cursor)) + setQuery(displaySlice(text, at() + 1, cursor)) return } @@ -623,19 +627,12 @@ export function createPromptState(input: PromptInput): PromptState { return } - const head = text.slice(0, cursor) - const idx = head.lastIndexOf("@") - if (idx === -1) { - return - } - - const before = idx === 0 ? undefined : head[idx - 1] - const tail = head.slice(idx) - if ((before === undefined || /\s/.test(before)) && !/\s/.test(tail)) { + const idx = mentionTriggerIndex(text, cursor) + if (idx !== undefined) { setAt(idx) menu.reset() setMode("mention") - setQuery(head.slice(idx + 1)) + setQuery(displaySlice(text, idx + 1, cursor)) } } @@ -782,7 +779,7 @@ export function createPromptState(input: PromptInput): PromptState { } const cursor = area.cursorOffset - const tail = area.plainText.at(cursor) + const tail = displayCharAt(area.plainText, cursor) const append = "@" + next.value + (tail === " " ? "" : " ") area.cursorOffset = at() const start = area.logicalCursor @@ -941,7 +938,8 @@ export function createPromptState(input: PromptInput): PromptState { } const dir = up ? -1 : 1 - if ((dir === -1 && area.cursorOffset === 0) || (dir === 1 && area.cursorOffset === area.plainText.length)) { + const endOffset = Bun.stringWidth(area.plainText) + if ((dir === -1 && area.cursorOffset === 0) || (dir === 1 && area.cursorOffset === endOffset)) { move(dir, event) return } @@ -955,7 +953,7 @@ export function createPromptState(input: PromptInput): PromptState { ? area.height - 1 : Math.max(0, (area.virtualLineCount ?? 1) - 1) if (dir === 1 && area.visualCursor.visualRow === end) { - area.cursorOffset = area.plainText.length + area.cursorOffset = endOffset } } diff --git a/packages/opencode/src/cli/cmd/run/prompt.shared.ts b/packages/opencode/src/cli/cmd/run/prompt.shared.ts index 1b639e6e7..0da787cb3 100644 --- a/packages/opencode/src/cli/cmd/run/prompt.shared.ts +++ b/packages/opencode/src/cli/cmd/run/prompt.shared.ts @@ -12,6 +12,7 @@ // The leader-key cycle (promptCycle) uses a two-step pattern: first press // arms the leader, second press within the timeout fires the action. import type { KeyBinding } from "@opentui/core" +export { displayCharAt, displaySlice, mentionTriggerIndex } from "../prompt-display" import { formatBinding, parseBindings } from "./keymap.shared" import type { FooterKeybinds, RunPrompt } from "./types" @@ -275,7 +276,7 @@ export function movePromptHistory(state: PromptHistoryState, dir: -1 | 1, text: return { state, apply: false } } - if (dir === 1 && cursor !== text.length) { + if (dir === 1 && cursor !== Bun.stringWidth(text)) { return { state, apply: false } } @@ -309,7 +310,7 @@ export function movePromptHistory(state: PromptHistoryState, dir: -1 | 1, text: index: null, }, text: state.draft, - cursor: state.draft.length, + cursor: Bun.stringWidth(state.draft), apply: true, } } @@ -320,7 +321,7 @@ export function movePromptHistory(state: PromptHistoryState, dir: -1 | 1, text: index: idx, }, text: state.items[idx].text, - cursor: dir === -1 ? 0 : state.items[idx].text.length, + cursor: dir === -1 ? 0 : Bun.stringWidth(state.items[idx].text), apply: true, } } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 3242de94d..3f7604653 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -20,6 +20,7 @@ import { useFrecency } from "./frecency" import { useBindings } from "../../keymap" import { Reference } from "@/reference/reference" import type { Config } from "@/config/config" +import { displayCharAt, mentionTriggerIndex } from "@/cli/cmd/prompt-display" function removeLineRange(input: string) { const hashIndex = input.lastIndexOf("#") @@ -159,7 +160,7 @@ export function Autocomplete(props: { const input = props.input() const currentCursorOffset = input.cursorOffset - const charAfterCursor = props.value.at(currentCursorOffset) + const charAfterCursor = displayCharAt(props.value, currentCursorOffset) const needsSpace = charAfterCursor !== " " const append = "@" + text + (needsSpace ? " " : "") @@ -787,13 +788,8 @@ export function Autocomplete(props: { } // Check for "@" trigger - find the nearest "@" before cursor with no whitespace between - const text = value.slice(0, offset) - const idx = text.lastIndexOf("@") - if (idx === -1) return - - const between = text.slice(idx) - const before = idx === 0 ? undefined : value[idx - 1] - if ((before === undefined || /\s/.test(before)) && !between.match(/\s/)) { + const idx = mentionTriggerIndex(value, offset) + if (idx !== undefined) { show("@") setStore("index", idx) } diff --git a/packages/opencode/test/cli/run/prompt.shared.test.ts b/packages/opencode/test/cli/run/prompt.shared.test.ts index 85a9dfa40..299751eaa 100644 --- a/packages/opencode/test/cli/run/prompt.shared.test.ts +++ b/packages/opencode/test/cli/run/prompt.shared.test.ts @@ -1,8 +1,11 @@ import { describe, expect, test } from "bun:test" import { createPromptHistory, + displayCharAt, + displaySlice, isExitCommand, isNewCommand, + mentionTriggerIndex, movePromptHistory, printableBinding, promptCycle, @@ -85,6 +88,53 @@ describe("run prompt shared", () => { expect(draft.state.index).toBeNull() }) + test("uses display-width cursors for history restoration", () => { + const base = createPromptHistory([prompt("one"), prompt("中文")]) + + const latest = movePromptHistory(base, -1, "草稿", 0) + expect(latest.apply).toBe(true) + expect(latest.text).toBe("中文") + expect(latest.cursor).toBe(0) + + const older = movePromptHistory(latest.state, -1, "中文", 0) + expect(older.apply).toBe(true) + expect(older.text).toBe("one") + expect(older.cursor).toBe(0) + + const newer = movePromptHistory(older.state, 1, "one", Bun.stringWidth("one")) + expect(newer.apply).toBe(true) + expect(newer.text).toBe("中文") + expect(newer.cursor).toBe(Bun.stringWidth("中文")) + + const draft = movePromptHistory(newer.state, 1, "中文", Bun.stringWidth("中文")) + expect(draft.apply).toBe(true) + expect(draft.text).toBe("草稿") + expect(draft.cursor).toBe(Bun.stringWidth("草稿")) + }) + + test("uses display-width offsets for mention helpers", () => { + expect(mentionTriggerIndex("@")).toBe(0) + expect(mentionTriggerIndex("test @")).toBe(5) + expect(mentionTriggerIndex("中文 @")).toBe(5) + expect(mentionTriggerIndex("こんにちは @")).toBe(11) + expect(mentionTriggerIndex("한국어 @")).toBe(7) + expect(mentionTriggerIndex("🙂 @")).toBe(3) + expect(mentionTriggerIndex("中文 @src file", Bun.stringWidth("中文 @src"))).toBe(5) + expect(displayCharAt("中文 @src", Bun.stringWidth("中文 @"))).toBe("s") + expect(displaySlice("中文 @src", 5, Bun.stringWidth("中文 @src"))).toBe("@src") + expect(displaySlice("中文 @src", 6, Bun.stringWidth("中文 @src"))).toBe("src") + expect(mentionTriggerIndex("👨‍👩‍👧‍👦 @src", Bun.stringWidth("👨‍👩‍👧‍👦 @src"))).toBe(3) + expect(displayCharAt("👨‍👩‍👧‍👦 @src", Bun.stringWidth("👨‍👩‍👧‍👦 @"))).toBe("s") + expect(displaySlice("👨‍👩‍👧‍👦 @src", 3, Bun.stringWidth("👨‍👩‍👧‍👦 @src"))).toBe("@src") + expect(mentionTriggerIndex("中文@")).toBeUndefined() + expect(mentionTriggerIndex("こんにちは@")).toBeUndefined() + expect(mentionTriggerIndex("한국어@")).toBeUndefined() + expect(mentionTriggerIndex("🙂@")).toBeUndefined() + expect(mentionTriggerIndex("hello@")).toBeUndefined() + expect(mentionTriggerIndex("foo@bar.com")).toBeUndefined() + expect(mentionTriggerIndex("中文 @src file")).toBeUndefined() + }) + test("handles direct and leader-based variant cycling", () => { const keys = promptKeys(keybinds) From 8feb4a31c75e8bd3bd8f84ec860cfd4d326479b4 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 12 May 2026 15:22:38 +0530 Subject: [PATCH 093/378] feat(core): add background job service (#27033) --- packages/opencode/src/background/job.ts | 200 ++++++++++++++++++ packages/opencode/src/effect/app-runtime.ts | 2 + packages/opencode/src/id/id.ts | 1 + packages/opencode/test/background/job.test.ts | 127 +++++++++++ 4 files changed, 330 insertions(+) create mode 100644 packages/opencode/src/background/job.ts create mode 100644 packages/opencode/test/background/job.test.ts diff --git a/packages/opencode/src/background/job.ts b/packages/opencode/src/background/job.ts new file mode 100644 index 000000000..3ea228f04 --- /dev/null +++ b/packages/opencode/src/background/job.ts @@ -0,0 +1,200 @@ +import { InstanceState } from "@/effect/instance-state" +import { Identifier } from "@/id/id" +import { Cause, Clock, Context, Deferred, Effect, Fiber, Layer, Scope, SynchronizedRef } from "effect" + +export type Status = "running" | "completed" | "error" | "cancelled" + +export type Info = { + id: string + type: string + title?: string + status: Status + started_at: number + completed_at?: number + output?: string + error?: string + metadata?: Record +} + +type Active = { + info: Info + done: Deferred.Deferred + fiber?: Fiber.Fiber +} + +type State = { + jobs: SynchronizedRef.SynchronizedRef> + scope: Scope.Scope +} + +type FinishResult = { + info?: Info + done?: Deferred.Deferred +} + +export type StartInput = { + id?: string + type: string + title?: string + metadata?: Record + run: Effect.Effect +} + +export type WaitInput = { + id: string + timeout?: number +} + +export type WaitResult = { + info?: Info + timedOut: boolean +} + +export interface Interface { + readonly list: () => Effect.Effect + readonly get: (id: string) => Effect.Effect + readonly start: (input: StartInput) => Effect.Effect + readonly wait: (input: WaitInput) => Effect.Effect + readonly cancel: (id: string) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/BackgroundJob") {} + +function snapshot(job: Active): Info { + return { + ...job.info, + ...(job.info.metadata ? { metadata: { ...job.info.metadata } } : {}), + } +} + +function errorText(error: unknown) { + if (error instanceof Error) return error.message + return String(error) +} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const state = yield* InstanceState.make( + Effect.fn("BackgroundJob.state")(function* () { + return { + jobs: yield* SynchronizedRef.make(new Map()), + scope: yield* Scope.Scope, + } + }), + ) + + const finish = Effect.fn("BackgroundJob.finish")(function* ( + id: string, + status: Exclude, + data?: { output?: string; error?: string }, + ) { + const completed_at = yield* Clock.currentTimeMillis + const result = yield* SynchronizedRef.modify( + (yield* InstanceState.get(state)).jobs, + (jobs): readonly [FinishResult, Map] => { + const job = jobs.get(id) + if (!job) return [{}, jobs] + if (job.info.status !== "running") return [{ info: snapshot(job) }, jobs] + const next = { + ...job, + fiber: undefined, + info: { + ...job.info, + status, + completed_at, + ...(data?.output !== undefined ? { output: data.output } : {}), + ...(data?.error !== undefined ? { error: data.error } : {}), + }, + } + return [{ info: snapshot(next), done: job.done }, new Map(jobs).set(id, next)] + }, + ) + if (result.info && result.done) yield* Deferred.succeed(result.done, result.info).pipe(Effect.ignore) + return result.info + }) + + const list: Interface["list"] = Effect.fn("BackgroundJob.list")(function* () { + return Array.from((yield* SynchronizedRef.get((yield* InstanceState.get(state)).jobs)).values()) + .map(snapshot) + .toSorted((a, b) => a.started_at - b.started_at) + }) + + const get: Interface["get"] = Effect.fn("BackgroundJob.get")(function* (id) { + const job = (yield* SynchronizedRef.get((yield* InstanceState.get(state)).jobs)).get(id) + if (!job) return + return snapshot(job) + }) + + const start: Interface["start"] = Effect.fn("BackgroundJob.start")(function* (input) { + return yield* Effect.uninterruptibleMask((restore) => + Effect.gen(function* () { + const s = yield* InstanceState.get(state) + const id = input.id ?? Identifier.ascending("job") + const started_at = yield* Clock.currentTimeMillis + const done = yield* Deferred.make() + return yield* SynchronizedRef.modifyEffect( + s.jobs, + Effect.fnUntraced(function* (jobs) { + const existing = jobs.get(id) + if (existing?.info.status === "running") return [snapshot(existing), jobs] as const + const fiber = yield* restore(input.run).pipe( + Effect.matchCauseEffect({ + onSuccess: (output) => finish(id, "completed", { output }), + onFailure: (cause) => + finish(id, Cause.hasInterruptsOnly(cause) ? "cancelled" : "error", { + error: errorText(Cause.squash(cause)), + }), + }), + Effect.asVoid, + Effect.forkIn(s.scope, { startImmediately: true }), + ) + const job = { + info: { + id, + type: input.type, + title: input.title, + status: "running" as const, + started_at, + metadata: input.metadata, + }, + done, + fiber, + } + return [snapshot(job), new Map(jobs).set(id, job)] as const + }), + ) + }), + ) + }) + + const wait: Interface["wait"] = Effect.fn("BackgroundJob.wait")(function* (input) { + const job = (yield* SynchronizedRef.get((yield* InstanceState.get(state)).jobs)).get(input.id) + if (!job) return { timedOut: false } + if (job.info.status !== "running") return { info: snapshot(job), timedOut: false } + if (input.timeout === undefined) return { info: yield* Deferred.await(job.done), timedOut: false } + if (input.timeout <= 0) return { info: snapshot(job), timedOut: true } + const info = yield* Deferred.await(job.done).pipe(Effect.timeoutOption(input.timeout)) + if (info._tag === "Some") return { info: info.value, timedOut: false } + return { info: snapshot(job), timedOut: true } + }) + + const cancel: Interface["cancel"] = Effect.fn("BackgroundJob.cancel")(function* (id) { + const job = (yield* SynchronizedRef.get((yield* InstanceState.get(state)).jobs)).get(id) + if (!job) return + if (job.info.status !== "running") return snapshot(job) + if (job.fiber) { + yield* Fiber.interrupt(job.fiber).pipe(Effect.ignore) + yield* Fiber.await(job.fiber).pipe(Effect.ignore) + } + const info = yield* finish(id, "cancelled") + return info + }) + + return Service.of({ list, get, start, wait, cancel }) + }), +) + +export const defaultLayer = layer + +export * as BackgroundJob from "./job" diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index 4c1637006..b0efab1ae 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -55,6 +55,7 @@ import { SyncEvent } from "@/sync" import { Npm } from "@opencode-ai/core/npm" import { memoMap } from "@opencode-ai/core/effect/memo-map" import { DataMigration } from "@/data-migration" +import { BackgroundJob } from "@/background/job" export const AppLayer = Layer.mergeAll( Npm.defaultLayer, @@ -81,6 +82,7 @@ export const AppLayer = Layer.mergeAll( Todo.defaultLayer, Session.defaultLayer, SessionStatus.defaultLayer, + BackgroundJob.defaultLayer, SessionRunState.defaultLayer, SessionProcessor.defaultLayer, SessionCompaction.defaultLayer, diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index 9e163cd6b..847a5c032 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -1,6 +1,7 @@ import { randomBytes } from "crypto" const prefixes = { + job: "job", event: "evt", session: "ses", message: "msg", diff --git a/packages/opencode/test/background/job.test.ts b/packages/opencode/test/background/job.test.ts new file mode 100644 index 000000000..afc7260bb --- /dev/null +++ b/packages/opencode/test/background/job.test.ts @@ -0,0 +1,127 @@ +import { describe, expect } from "bun:test" +import { Deferred, Effect } from "effect" +import { BackgroundJob } from "@/background/job" +import { testEffect } from "../lib/effect" + +const it = testEffect(BackgroundJob.defaultLayer) + +describe("background.job", () => { + it.instance("tracks started jobs through completion", () => + Effect.gen(function* () { + const jobs = yield* BackgroundJob.Service + const latch = yield* Deferred.make() + const job = yield* jobs.start({ + type: "test", + title: "test job", + run: Deferred.await(latch).pipe(Effect.as("done")), + }) + + expect(job.id.startsWith("job_")).toBe(true) + expect(job.status).toBe("running") + expect(job.title).toBe("test job") + + yield* Deferred.succeed(latch, undefined) + const done = yield* jobs.wait({ id: job.id }) + + expect(done.timedOut).toBe(false) + expect(done.info?.status).toBe("completed") + expect(done.info?.output).toBe("done") + expect((yield* jobs.list()).map((item) => item.id)).toEqual([job.id]) + }), + ) + + it.instance("returns a running snapshot when wait times out", () => + Effect.gen(function* () { + const jobs = yield* BackgroundJob.Service + const job = yield* jobs.start({ + type: "test", + run: Effect.never, + }) + + const result = yield* jobs.wait({ id: job.id, timeout: 1 }) + + expect(result.timedOut).toBe(true) + expect(result.info?.status).toBe("running") + }), + ) + + it.instance("deduplicates concurrent starts for a running id", () => + Effect.gen(function* () { + const jobs = yield* BackgroundJob.Service + const started = yield* Deferred.make() + const id = "job_test" + const [first, second] = yield* Effect.all( + [ + jobs.start({ + id, + type: "test", + run: Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)), + }), + jobs.start({ + id, + type: "test", + run: Effect.fail(new Error("duplicate started")), + }), + ], + { concurrency: "unbounded" }, + ) + + yield* Deferred.await(started) + + expect(first.id).toBe(id) + expect(second.id).toBe(id) + expect(first.status).toBe("running") + expect(second.status).toBe("running") + expect((yield* jobs.list()).map((item) => item.id)).toEqual([id]) + + yield* jobs.cancel(id) + }), + ) + + it.instance("records failed jobs", () => + Effect.gen(function* () { + const jobs = yield* BackgroundJob.Service + const job = yield* jobs.start({ + type: "test", + run: Effect.fail(new Error("boom")), + }) + + const result = yield* jobs.wait({ id: job.id }) + + expect(result.info?.status).toBe("error") + expect(result.info?.error).toBe("boom") + }), + ) + + it.instance("can cancel running jobs", () => + Effect.gen(function* () { + const jobs = yield* BackgroundJob.Service + const interrupted = yield* Deferred.make() + const job = yield* jobs.start({ + type: "test", + run: Effect.never.pipe(Effect.ensuring(Deferred.succeed(interrupted, undefined))), + }) + + const cancelled = yield* jobs.cancel(job.id) + + expect(cancelled?.status).toBe("cancelled") + yield* Deferred.await(interrupted).pipe(Effect.timeout("1 second")) + expect((yield* jobs.get(job.id))?.status).toBe("cancelled") + }), + ) + + it.instance("returns immutable snapshots", () => + Effect.gen(function* () { + const jobs = yield* BackgroundJob.Service + const job = yield* jobs.start({ + type: "test", + metadata: { value: "initial" }, + run: Effect.succeed("done"), + }) + + if (job.metadata) job.metadata.value = "changed" + + expect((yield* jobs.get(job.id))?.metadata?.value).toBe("initial") + }), + ) +}) From 28f38fc871091d3f0a9b360d3e256564603f6517 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 09:20:15 -0400 Subject: [PATCH 094/378] Remove Zod from named errors (#26982) --- packages/core/src/util/error.ts | 53 ++++++++------- .../enterprise/src/routes/share/[shareID].tsx | 30 ++++++--- packages/opencode/src/cli/ui.ts | 4 +- packages/opencode/src/config/config.ts | 14 ++-- packages/opencode/src/config/error.ts | 30 +++++---- packages/opencode/src/config/markdown.ts | 13 ++-- packages/opencode/src/config/parse.ts | 11 ++-- packages/opencode/src/ide/index.ts | 12 ++-- packages/opencode/src/index.ts | 18 ++++-- packages/opencode/src/lsp/client.ts | 10 +-- packages/opencode/src/mcp/index.ts | 9 +-- packages/opencode/src/session/message-v2.ts | 4 +- packages/opencode/src/session/message.ts | 25 ++------ packages/opencode/src/session/retry.ts | 5 +- packages/opencode/src/skill/index.ts | 55 +++++++++------- packages/opencode/src/storage/db.ts | 11 ++-- packages/opencode/src/storage/storage.ts | 10 +-- .../opencode/src/util/named-schema-error.ts | 46 +------------ packages/opencode/src/worktree/index.ts | 64 ++++++------------- packages/opencode/test/util/error.test.ts | 18 ++++++ 20 files changed, 197 insertions(+), 245 deletions(-) diff --git a/packages/core/src/util/error.ts b/packages/core/src/util/error.ts index 9d3b7c661..7338571f2 100644 --- a/packages/core/src/util/error.ts +++ b/packages/core/src/util/error.ts @@ -1,8 +1,8 @@ -import z from "zod" +import { Schema } from "effect" export abstract class NamedError extends Error { - abstract schema(): z.core.$ZodType - abstract toObject(): { name: string; data: any } + abstract schema(): Schema.Top + abstract toObject(): { name: string; data: unknown } static hasName(error: unknown, name: string): boolean { return ( @@ -10,30 +10,42 @@ export abstract class NamedError extends Error { ) } - static create(name: Name, data: Data) { - const schema = z - .object({ - name: z.literal(name), - data, - }) - .meta({ - ref: name, - }) + static create( + name: Name, + fields: Fields, + ): ReturnType>> + static create( + name: Name, + data: DataSchema, + ): ReturnType> + static create(name: Name, data: Schema.Top | Schema.Struct.Fields) { + return NamedError.createSchemaClass(name, Schema.isSchema(data) ? data : Schema.Struct(data)) + } + + private static createSchemaClass(name: Name, data: DataSchema) { + const schema = Schema.Struct({ + name: Schema.Literal(name), + data, + }).annotate({ identifier: name }) + type Data = Schema.Schema.Type + const result = class extends NamedError { public static readonly Schema = schema + public static readonly EffectSchema = schema + public static readonly tag = name - public override readonly name = name as Name + public override readonly name = name constructor( - public readonly data: z.input, + public readonly data: Data, options?: ErrorOptions, ) { super(name, options) this.name = name } - static isInstance(input: any): input is InstanceType { - return typeof input === "object" && "name" in input && input.name === name + static isInstance(input: unknown): input is InstanceType { + return NamedError.hasName(input, name) } schema() { @@ -51,10 +63,7 @@ export abstract class NamedError extends Error { return result } - public static readonly Unknown = NamedError.create( - "UnknownError", - z.object({ - message: z.string(), - }), - ) + public static readonly Unknown = NamedError.create("UnknownError", { + message: Schema.String, + }) } diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index b12afce27..7cfb2bb4a 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -15,7 +15,6 @@ import { Binary } from "@opencode-ai/core/util/binary" import { NamedError } from "@opencode-ai/core/util/error" import { DateTime } from "luxon" import { createStore } from "solid-js/store" -import z from "zod" import NotFound from "../[...404]" import { Tabs } from "@opencode-ai/ui/tabs" import { MessageNav } from "@opencode-ai/ui/message-nav" @@ -33,13 +32,28 @@ const ClientOnlyWorkerPoolProvider = clientOnly(() => })), ) -const SessionDataMissingError = NamedError.create( - "SessionDataMissingError", - z.object({ - sessionID: z.string(), - message: z.string().optional(), - }), -) +class SessionDataMissingError extends NamedError { + public override readonly name = "SessionDataMissingError" + + constructor( + public readonly data: { sessionID: string; message?: string }, + options?: ErrorOptions, + ) { + super("SessionDataMissingError", options) + } + + static isInstance(input: unknown): input is SessionDataMissingError { + return NamedError.hasName(input, "SessionDataMissingError") + } + + schema(): never { + throw new Error("SessionDataMissingError does not expose a schema") + } + + toObject() { + return { name: this.name, data: this.data } + } +} const getData = query(async (shareID) => { "use server" diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts index 7b4cf7f34..69e04b925 100644 --- a/packages/opencode/src/cli/ui.ts +++ b/packages/opencode/src/cli/ui.ts @@ -1,6 +1,6 @@ -import z from "zod" import { EOL } from "os" import { NamedError } from "@opencode-ai/core/util/error" +import { Schema } from "effect" import { logo as glyphs } from "./logo" const wordmark = [ @@ -10,7 +10,7 @@ const wordmark = [ `▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`, ] -export const CancelledError = NamedError.create("UICancelledError", z.void()) +export const CancelledError = NamedError.create("UICancelledError", Schema.optional(Schema.Void)) export const Style = { TEXT_HIGHLIGHT: "\x1b[96m", diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index e44405f42..d00c97f46 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -2,7 +2,6 @@ import * as Log from "@opencode-ai/core/util/log" import path from "path" import { pathToFileURL } from "url" import os from "os" -import z from "zod" import { mergeDeep } from "remeda" import { Global } from "@opencode-ai/core/global" import fsNode from "fs/promises" @@ -357,14 +356,11 @@ function writableGlobal(info: Info) { return next } -export const ConfigDirectoryTypoError = NamedError.create( - "ConfigDirectoryTypoError", - z.object({ - path: z.string(), - dir: z.string(), - suggestion: z.string(), - }), -) +export const ConfigDirectoryTypoError = NamedError.create("ConfigDirectoryTypoError", { + path: Schema.String, + dir: Schema.String, + suggestion: Schema.String, +}) export const layer = Layer.effect( Service, diff --git a/packages/opencode/src/config/error.ts b/packages/opencode/src/config/error.ts index c43598048..17d74fc1c 100644 --- a/packages/opencode/src/config/error.ts +++ b/packages/opencode/src/config/error.ts @@ -1,21 +1,23 @@ export * as ConfigError from "./error" -import z from "zod" import { NamedError } from "@opencode-ai/core/util/error" +import { Schema } from "effect" -export const JsonError = NamedError.create( - "ConfigJsonError", - z.object({ - path: z.string(), - message: z.string().optional(), +const Issue = Schema.StructWithRest( + Schema.Struct({ + message: Schema.String, + path: Schema.Array(Schema.String), }), + [Schema.Record(Schema.String, Schema.Unknown)], ) -export const InvalidError = NamedError.create( - "ConfigInvalidError", - z.object({ - path: z.string(), - issues: z.custom().optional(), - message: z.string().optional(), - }), -) +export const JsonError = NamedError.create("ConfigJsonError", { + path: Schema.String, + message: Schema.optional(Schema.String), +}) + +export const InvalidError = NamedError.create("ConfigInvalidError", { + path: Schema.String, + issues: Schema.optional(Schema.Array(Issue)), + message: Schema.optional(Schema.String), +}) diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts index 390f7f8b0..820f4bf64 100644 --- a/packages/opencode/src/config/markdown.ts +++ b/packages/opencode/src/config/markdown.ts @@ -1,6 +1,6 @@ import { NamedError } from "@opencode-ai/core/util/error" import matter from "gray-matter" -import { z } from "zod" +import { Schema } from "effect" import { Filesystem } from "@/util/filesystem" export const FILE_REGEX = /(?>( keys: extra, path: [], message: `Unrecognized key${extra.length === 1 ? "" : "s"}: ${extra.join(", ")}`, - } as z.core.$ZodIssue, + }, ], }) } @@ -61,8 +60,12 @@ export function schema>( { path: source, issues: EffectSchema.isSchemaError(error) - ? (SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues as z.core.$ZodIssue[]) - : ([{ code: "custom", message: String(error), path: [] }] as z.core.$ZodIssue[]), + ? SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues.map((issue) => ({ + ...issue, + message: issue.message, + path: issue.path?.map(String) ?? [], + })) + : [{ message: String(error), path: [] }], }, { cause: error }, ) diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index 2df293f16..a31c5bd05 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -1,5 +1,4 @@ import { BusEvent } from "@/bus/bus-event" -import z from "zod" import { Schema } from "effect" import { NamedError } from "@opencode-ai/core/util/error" import * as Log from "@opencode-ai/core/util/log" @@ -24,14 +23,11 @@ export const Event = { ), } -export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", z.object({})) +export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", {}) -export const InstallFailedError = NamedError.create( - "InstallFailedError", - z.object({ - stderr: z.string(), - }), -) +export const InstallFailedError = NamedError.create("InstallFailedError", { + stderr: Schema.String, +}) export function ide() { if (process.env["TERM_PROGRAM"] === "vscode") { diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 4c8e44704..d20f29dd4 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -39,6 +39,7 @@ import { PluginCommand } from "./cli/cmd/plug" import { Heap } from "./cli/heap" import { drizzle } from "drizzle-orm/bun-sqlite" import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process" +import { isRecord } from "@/util/record" const processMetadata = ensureProcessMetadata("main") @@ -203,13 +204,6 @@ try { } } catch (e) { let data: Record = {} - if (e instanceof NamedError) { - const obj = e.toObject() - Object.assign(data, { - ...obj.data, - }) - } - if (e instanceof Error) { Object.assign(data, { name: e.name, @@ -219,6 +213,16 @@ try { }) } + if (e instanceof NamedError) { + const obj = e.toObject() + if (isRecord(obj.data)) { + for (const [key, value] of Object.entries(obj.data)) { + if (key === "name" || key === "stack" || key === "cause") continue + data[key] = value + } + } + } + if (e instanceof ResolveMessage) { Object.assign(data, { name: e.name, diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index 809ea9509..ac9706fc3 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -7,7 +7,6 @@ import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types import * as Log from "@opencode-ai/core/util/log" import { Process } from "@/util/process" import { LANGUAGE_EXTENSIONS } from "./language" -import z from "zod" import { Schema } from "effect" import type * as LSPServer from "./server" import { NamedError } from "@opencode-ai/core/util/error" @@ -32,12 +31,9 @@ export type Info = NonNullable>> export type Diagnostic = VSCodeDiagnostic -export const InitializeError = NamedError.create( - "LSPInitializeError", - z.object({ - serverID: z.string(), - }), -) +export const InitializeError = NamedError.create("LSPInitializeError", { + serverID: Schema.String, +}) export const Event = { Diagnostics: BusEvent.define( diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index db43412f7..992825dd6 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -68,12 +68,9 @@ export const BrowserOpenFailed = BusEvent.define( }), ) -export const Failed = NamedError.create( - "MCPFailed", - z.object({ - name: z.string(), - }), -) +export const Failed = NamedError.create("MCPFailed", { + name: Schema.String, +}) type MCPClient = Client diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 4dae82038..f797e2dc3 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -382,9 +382,7 @@ export type Part = const AssistantErrorSchema = Schema.Union([ AuthError.EffectSchema, - Schema.Struct({ name: Schema.Literal("UnknownError"), data: Schema.Struct({ message: Schema.String }) }).annotate({ - identifier: "UnknownError", - }), + NamedError.Unknown.EffectSchema, OutputLengthError.EffectSchema, AbortedError.EffectSchema, StructuredOutputError.EffectSchema, diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts index 16c010003..6a859ffaa 100644 --- a/packages/opencode/src/session/message.ts +++ b/packages/opencode/src/session/message.ts @@ -3,6 +3,7 @@ import { SessionID } from "./schema" import { ModelID, ProviderID } from "../provider/schema" import { NonNegativeInt } from "@opencode-ai/core/schema" import { namedSchemaError } from "@/util/named-schema-error" +import { NamedError } from "@opencode-ai/core/util/error" export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}) export const AuthError = namedSchemaError("ProviderAuthError", { @@ -10,26 +11,6 @@ export const AuthError = namedSchemaError("ProviderAuthError", { message: Schema.String, }) -const AuthErrorEffect = Schema.Struct({ - name: Schema.Literal("ProviderAuthError"), - data: Schema.Struct({ - providerID: Schema.String, - message: Schema.String, - }), -}) - -const OutputLengthErrorEffect = Schema.Struct({ - name: Schema.Literal("MessageOutputLengthError"), - data: Schema.Struct({}), -}) - -const UnknownErrorEffect = Schema.Struct({ - name: Schema.Literal("UnknownError"), - data: Schema.Struct({ - message: Schema.String, - }), -}) - export const ToolCall = Schema.Struct({ state: Schema.Literal("call"), step: Schema.optional(NonNegativeInt), @@ -124,7 +105,9 @@ export const Info = Schema.Struct({ created: NonNegativeInt, completed: Schema.optional(NonNegativeInt), }), - error: Schema.optional(Schema.Union([AuthErrorEffect, UnknownErrorEffect, OutputLengthErrorEffect])), + error: Schema.optional( + Schema.Union([AuthError.EffectSchema, NamedError.Unknown.EffectSchema, OutputLengthError.EffectSchema]), + ), sessionID: SessionID, tool: Schema.Record( Schema.String, diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 1f73dee31..463bc27a9 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -2,6 +2,7 @@ import type { NamedError } from "@opencode-ai/core/util/error" import { Cause, Clock, Duration, Effect, Schedule } from "effect" import { MessageV2 } from "./message-v2" import { iife } from "@/util/iife" +import { isRecord } from "@/util/record" export type Err = ReturnType @@ -121,7 +122,7 @@ export function retryable(error: Err, provider: string) { } // Check for rate limit patterns in plain text error messages - const msg = error.data?.message + const msg = isRecord(error.data) ? error.data.message : undefined if (typeof msg === "string") { const lower = msg.toLowerCase() if ( @@ -133,7 +134,7 @@ export function retryable(error: Err, provider: string) { } } - const json = parseJSON(error.data?.message) + const json = parseJSON(msg) if (!json || typeof json !== "object") return undefined const code = typeof json.code === "string" ? json.code : "" diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index a0cc383d0..59dfeb080 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -1,6 +1,5 @@ import path from "path" import { pathToFileURL } from "url" -import z from "zod" import { Effect, Layer, Context, Schema } from "effect" import { NamedError } from "@opencode-ai/core/util/error" import type { Agent } from "@/agent/agent" @@ -16,6 +15,7 @@ import { Glob } from "@opencode-ai/core/util/glob" import * as Log from "@opencode-ai/core/util/log" import { Discovery } from "./discovery" import CUSTOMIZE_OPENCODE_SKILL_BODY from "./prompt/customize-opencode.md" with { type: "text" } +import { isRecord } from "@/util/record" const log = Log.create({ service: "skill" }) const CLAUDE_EXTERNAL_DIR = ".claude" @@ -41,23 +41,33 @@ export const Info = Schema.Struct({ }) export type Info = Schema.Schema.Type -export const InvalidError = NamedError.create( - "SkillInvalidError", - z.object({ - path: z.string(), - message: z.string().optional(), - issues: z.custom().optional(), +const Issue = Schema.StructWithRest( + Schema.Struct({ + message: Schema.String, + path: Schema.Array(Schema.String), }), + [Schema.Record(Schema.String, Schema.Unknown)], ) -export const NameMismatchError = NamedError.create( - "SkillNameMismatchError", - z.object({ - path: z.string(), - expected: z.string(), - actual: z.string(), - }), -) +function isSkillFrontmatter(data: unknown): data is { name: string; description?: string } { + return ( + isRecord(data) && + typeof data.name === "string" && + (data.description === undefined || typeof data.description === "string") + ) +} + +export const InvalidError = NamedError.create("SkillInvalidError", { + path: Schema.String, + message: Schema.optional(Schema.String), + issues: Schema.optional(Schema.Array(Issue)), +}) + +export const NameMismatchError = NamedError.create("SkillNameMismatchError", { + path: Schema.String, + expected: Schema.String, + actual: Schema.String, +}) type State = { skills: Record @@ -101,21 +111,20 @@ const add = Effect.fnUntraced(function* (state: State, match: string, bus: Bus.I if (!md) return - const parsed = z.object({ name: z.string(), description: z.string().optional() }).safeParse(md.data) - if (!parsed.success) return + if (!isSkillFrontmatter(md.data)) return - if (state.skills[parsed.data.name]) { + if (state.skills[md.data.name]) { log.warn("duplicate skill name", { - name: parsed.data.name, - existing: state.skills[parsed.data.name].location, + name: md.data.name, + existing: state.skills[md.data.name].location, duplicate: match, }) } state.dirs.add(path.dirname(match)) - state.skills[parsed.data.name] = { - name: parsed.data.name, - description: parsed.data.description, + state.skills[md.data.name] = { + name: md.data.name, + description: md.data.description, location: match, content: md.content, } diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index 06cb99f97..86e14da56 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -7,7 +7,6 @@ import { lazy } from "../util/lazy" import { Global } from "@opencode-ai/core/global" import * as Log from "@opencode-ai/core/util/log" import { NamedError } from "@opencode-ai/core/util/error" -import z from "zod" import path from "path" import { readFileSync, readdirSync, existsSync } from "fs" import { Flag } from "@opencode-ai/core/flag/flag" @@ -15,15 +14,13 @@ import { InstallationChannel } from "@opencode-ai/core/installation/version" import { InstanceState } from "@/effect/instance-state" import { iife } from "@/util/iife" import { init } from "#db" +import { Schema } from "effect" declare const OPENCODE_MIGRATIONS: { sql: string; timestamp: number; name: string }[] | undefined -export const NotFoundError = NamedError.create( - "NotFoundError", - z.object({ - message: z.string(), - }), -) +export const NotFoundError = NamedError.create("NotFoundError", { + message: Schema.String, +}) const log = Log.create({ service: "db" }) diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts index bc4d8b8f1..e1f5f681b 100644 --- a/packages/opencode/src/storage/storage.ts +++ b/packages/opencode/src/storage/storage.ts @@ -2,7 +2,6 @@ import * as Log from "@opencode-ai/core/util/log" import path from "path" import { Global } from "@opencode-ai/core/global" import { NamedError } from "@opencode-ai/core/util/error" -import z from "zod" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect" import { NonNegativeInt } from "@opencode-ai/core/schema" @@ -16,12 +15,9 @@ type Migration = ( git: Git.Interface, ) => Effect.Effect -export const NotFoundError = NamedError.create( - "NotFoundError", - z.object({ - message: z.string(), - }), -) +export const NotFoundError = NamedError.create("NotFoundError", { + message: Schema.String, +}) export type Error = AppFileSystem.Error | InstanceType diff --git a/packages/opencode/src/util/named-schema-error.ts b/packages/opencode/src/util/named-schema-error.ts index a5ff0828e..cc02c3731 100644 --- a/packages/opencode/src/util/named-schema-error.ts +++ b/packages/opencode/src/util/named-schema-error.ts @@ -1,51 +1,9 @@ import { Schema } from "effect" +import { NamedError } from "@opencode-ai/core/util/error" /** * Create a Schema-backed NamedError-shaped class. - * - * Drop-in replacement for `NamedError.create(tag, zodShape)` but backed by - * `Schema.Struct` under the hood. The wire shape emitted by the derived - * `.Schema` is still `{ name: tag, data: {...fields} }` so the generated - * OpenAPI/SDK output is byte-identical to the original NamedError schema. - * - * Preserves the existing surface: - * - static `Schema` (Effect schema of the wire shape) - * - static `isInstance(x)` - * - instance `toObject()` returning `{ name, data }` - * - `new X({ ...data }, { cause })` */ export function namedSchemaError(tag: Tag, fields: Fields) { - const dataSchema = Schema.Struct(fields) - // Wire shape matches the original NamedError output so the SDK stays stable. - const effectSchema = Schema.Struct({ - name: Schema.Literal(tag), - data: dataSchema, - }).annotate({ identifier: tag }) - - type Data = Schema.Schema.Type - - class NamedSchemaError extends Error { - static readonly Schema = effectSchema - static readonly EffectSchema = effectSchema - static readonly tag = tag - public static isInstance(input: unknown): input is NamedSchemaError { - return typeof input === "object" && input !== null && "name" in input && (input as { name: unknown }).name === tag - } - - public override readonly name: Tag = tag - public readonly data: Data - - constructor(data: Data, options?: ErrorOptions) { - super(tag, options) - this.data = data - } - - toObject(): { name: Tag; data: Data } { - return { name: tag, data: this.data } - } - } - - Object.defineProperty(NamedSchemaError, "name", { value: tag }) - - return NamedSchemaError + return NamedError.create(tag, fields) } diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 439f36e0a..7d0218926 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -1,4 +1,3 @@ -import z from "zod" import { NamedError } from "@opencode-ai/core/util/error" import { Global } from "@opencode-ai/core/global" import { InstanceLayer } from "@/project/instance-layer" @@ -65,54 +64,33 @@ export const ResetInput = Schema.Struct({ }).annotate({ identifier: "WorktreeResetInput" }) export type ResetInput = Schema.Schema.Type -export const NotGitError = NamedError.create( - "WorktreeNotGitError", - z.object({ - message: z.string(), - }), -) +export const NotGitError = NamedError.create("WorktreeNotGitError", { + message: Schema.String, +}) -export const NameGenerationFailedError = NamedError.create( - "WorktreeNameGenerationFailedError", - z.object({ - message: z.string(), - }), -) +export const NameGenerationFailedError = NamedError.create("WorktreeNameGenerationFailedError", { + message: Schema.String, +}) -export const CreateFailedError = NamedError.create( - "WorktreeCreateFailedError", - z.object({ - message: z.string(), - }), -) +export const CreateFailedError = NamedError.create("WorktreeCreateFailedError", { + message: Schema.String, +}) -export const StartCommandFailedError = NamedError.create( - "WorktreeStartCommandFailedError", - z.object({ - message: z.string(), - }), -) +export const StartCommandFailedError = NamedError.create("WorktreeStartCommandFailedError", { + message: Schema.String, +}) -export const RemoveFailedError = NamedError.create( - "WorktreeRemoveFailedError", - z.object({ - message: z.string(), - }), -) +export const RemoveFailedError = NamedError.create("WorktreeRemoveFailedError", { + message: Schema.String, +}) -export const ResetFailedError = NamedError.create( - "WorktreeResetFailedError", - z.object({ - message: z.string(), - }), -) +export const ResetFailedError = NamedError.create("WorktreeResetFailedError", { + message: Schema.String, +}) -export const ListFailedError = NamedError.create( - "WorktreeListFailedError", - z.object({ - message: z.string(), - }), -) +export const ListFailedError = NamedError.create("WorktreeListFailedError", { + message: Schema.String, +}) function slugify(input: string) { return input diff --git a/packages/opencode/test/util/error.test.ts b/packages/opencode/test/util/error.test.ts index e7a02d615..bc966133a 100644 --- a/packages/opencode/test/util/error.test.ts +++ b/packages/opencode/test/util/error.test.ts @@ -1,5 +1,9 @@ import { describe, expect, test } from "bun:test" +import { Schema } from "effect" +import { NamedError } from "@opencode-ai/core/util/error" import { errorData, errorFormat, errorMessage } from "../../src/util/error" +import { namedSchemaError } from "../../src/util/named-schema-error" +import { UI } from "../../src/cli/ui" describe("util.error", () => { test("formats native Error instances", () => { @@ -48,4 +52,18 @@ describe("util.error", () => { expect(data.message).toBe("ResolveMessage: Cannot resolve module") expect(String(data.formatted)).toContain("ResolveMessage") }) + + test("named schema errors are real NamedError instances", () => { + const ExampleError = namedSchemaError("ExampleError", { message: Schema.String }) + const error = new ExampleError({ message: "boom" }) + + expect(error).toBeInstanceOf(NamedError) + expect(error.toObject()).toEqual({ name: "ExampleError", data: { message: "boom" } }) + }) + + test("void named errors accept JSON without data", () => { + const serialized = JSON.parse(JSON.stringify(new UI.CancelledError(undefined).toObject())) + + expect(Schema.decodeUnknownOption(UI.CancelledError.Schema)(serialized)._tag).toBe("Some") + }) }) From 0de3b67cc04102c895a9138049083cc81b81be8b Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 09:28:46 -0400 Subject: [PATCH 095/378] test(tool): migrate shell tests to Effect runner (#26968) --- packages/opencode/test/tool/shell.test.ts | 1603 ++++++++++----------- 1 file changed, 760 insertions(+), 843 deletions(-) diff --git a/packages/opencode/test/tool/shell.test.ts b/packages/opencode/test/tool/shell.test.ts index 287844141..4b4cc8c88 100644 --- a/packages/opencode/test/tool/shell.test.ts +++ b/packages/opencode/test/tool/shell.test.ts @@ -1,14 +1,13 @@ -import { describe, expect, test } from "bun:test" -import { Effect, Layer, ManagedRuntime } from "effect" +import { describe, expect } from "bun:test" +import { Cause, Effect, Exit, Layer } from "effect" +import type * as Scope from "effect/Scope" import os from "os" import path from "path" import { Config } from "@/config/config" import { Shell } from "../../src/shell/shell" import { ShellTool } from "../../src/tool/shell" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { Filesystem } from "@/util/filesystem" -import { tmpdir } from "../fixture/fixture" +import { provideInstance, tmpdirScoped } from "../fixture/fixture" import type { Permission } from "../../src/permission" import { Agent } from "../../src/agent/agent" import { Truncate } from "@/tool/truncate" @@ -16,23 +15,48 @@ import { SessionID, MessageID } from "../../src/session/schema" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Plugin } from "../../src/plugin" +import { testEffect } from "../lib/effect" +import { Tool } from "@/tool/tool" -const runtime = ManagedRuntime.make( - Layer.mergeAll( - CrossSpawnSpawner.defaultLayer, - AppFileSystem.defaultLayer, - Plugin.defaultLayer, - Truncate.defaultLayer, - Config.defaultLayer, - Agent.defaultLayer, - ), +const shellLayer = Layer.mergeAll( + CrossSpawnSpawner.defaultLayer, + AppFileSystem.defaultLayer, + Plugin.defaultLayer, + Truncate.defaultLayer, + Config.defaultLayer, + Agent.defaultLayer, ) +const it = testEffect(shellLayer) +type ShellTestServices = (typeof shellLayer extends Layer.Layer ? ROut : never) | Scope.Scope -function initBash() { - return runtime.runPromise(ShellTool.pipe(Effect.flatMap((info) => info.init()))) -} +const initShell = Effect.fn("ShellToolTest.init")(function* () { + const info = yield* ShellTool + return yield* info.init() +}) -const initShell = initBash +const initBash = initShell + +const run = Effect.fn("ShellToolTest.run")(function* ( + args: Tool.InferParameters, + next: Tool.Context = ctx, +) { + const bash = yield* initShell() + return yield* bash.execute(args, next) +}) + +const runIn = (directory: string, self: Effect.Effect) => self.pipe(provideInstance(directory)) + +const fail = Effect.fn("ShellToolTest.fail")(function* ( + args: Tool.InferParameters, + next: Tool.Context = ctx, +) { + const exit = yield* run(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 command to fail") +}) const ctx = { sessionID: SessionID.make("ses_test"), @@ -96,27 +120,31 @@ const forms = (dir: string) => { return Array.from(new Set([full, slash, root, root.toLowerCase()])) } -const withShell = (item: { label: string; shell: string }, fn: () => Promise) => async () => { - const prev = process.env.SHELL - process.env.SHELL = item.shell - Shell.acceptable.reset() - Shell.preferred.reset() - try { - await fn() - } finally { - if (prev === undefined) delete process.env.SHELL - else process.env.SHELL = prev - Shell.acceptable.reset() - Shell.preferred.reset() - } -} +const withShell = (item: { label: string; shell: string }, self: Effect.Effect) => + Effect.acquireUseRelease( + Effect.sync(() => { + const prev = process.env.SHELL + process.env.SHELL = item.shell + Shell.acceptable.reset() + Shell.preferred.reset() + return prev + }), + () => self, + (prev) => + Effect.sync(() => { + if (prev === undefined) delete process.env.SHELL + else process.env.SHELL = prev + Shell.acceptable.reset() + Shell.preferred.reset() + }), + ) -const each = (name: string, fn: (item: { label: string; shell: string }) => Promise) => { +const each = ( + name: string, + fn: (item: { label: string; shell: string }) => Effect.Effect, +) => { for (const item of shells) { - test( - `${name} [${item.label}]`, - withShell(item, () => fn(item)), - ) + it.live(`${name} [${item.label}]`, () => withShell(item, fn(item))) } } @@ -140,277 +168,248 @@ const mustTruncate = (result: { } describe("tool.shell", () => { - each("basic", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() - const result = await Effect.runPromise( - bash.execute( - { - command: "echo test", - description: "Echo test message", - }, - ctx, - ), - ) + each("basic", () => + runIn( + projectRoot, + Effect.gen(function* () { + const result = yield* run({ + command: "echo test", + description: "Echo test message", + }) expect(result.metadata.exit).toBe(0) expect(result.metadata.output).toContain("test") - }, - }) - }) + }), + ), + ) - test("falls back from terminal-only configured shell", async () => { - await using tmp = await tmpdir({ - config: { shell: "fish" }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const fallback = Shell.name(Shell.acceptable("fish")) - expect(fallback).not.toBe("fish") - expect(bash.description).toContain(fallback) + it.live("falls back from terminal-only configured shell", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped({ config: { shell: "fish" } }) + yield* runIn( + tmp, + Effect.gen(function* () { + const bash = yield* initBash() + const fallback = Shell.name(Shell.acceptable("fish")) + expect(fallback).not.toBe("fish") + expect(bash.description).toContain(fallback) - const result = await Effect.runPromise( - bash.execute( + const result = yield* bash.execute( { command: "echo fallback", description: "Echo fallback text", }, ctx, - ), - ) - expect(result.metadata.exit).toBe(0) - expect(result.output).toContain("fallback") - }, - }) - }) + ) + expect(result.metadata.exit).toBe(0) + expect(result.output).toContain("fallback") + }), + ) + }), + ) }) describe("tool.shell permissions", () => { - each("asks for bash permission with correct pattern", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initShell() - const requests: Array> = [] - await Effect.runPromise( - bash.execute( + each("asks for bash permission with correct pattern", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const requests: Array> = [] + yield* run( { command: "echo hello", description: "Echo hello", }, capture(requests), - ), - ) - expect(requests.length).toBe(1) - expect(requests[0].permission).toBe("bash") - expect(requests[0].patterns).toContain("echo hello") - }, - }) - }) + ) + expect(requests.length).toBe(1) + expect(requests[0].permission).toBe("bash") + expect(requests[0].patterns).toContain("echo hello") + }), + ) + }), + ) - each("asks for bash permission with multiple commands", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initShell() - const requests: Array> = [] - await Effect.runPromise( - bash.execute( + each("asks for bash permission with multiple commands", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const requests: Array> = [] + yield* run( { command: "echo foo && echo bar", description: "Echo twice", }, capture(requests), - ), - ) - expect(requests.length).toBe(1) - expect(requests[0].permission).toBe("bash") - expect(requests[0].patterns).toContain("echo foo") - expect(requests[0].patterns).toContain("echo bar") - }, - }) - }) + ) + expect(requests.length).toBe(1) + expect(requests[0].permission).toBe("bash") + expect(requests[0].patterns).toContain("echo foo") + expect(requests[0].patterns).toContain("echo bar") + }), + ) + }), + ) for (const item of ps) { - test( - `parses PowerShell conditionals for permission prompts [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live(`parses PowerShell conditionals for permission prompts [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const requests: Array> = [] - await Effect.runPromise( - bash.execute( - { - command: "Write-Host foo; if ($?) { Write-Host bar }", - description: "Check PowerShell conditional", - }, - capture(requests), - ), + yield* run( + { + command: "Write-Host foo; if ($?) { Write-Host bar }", + description: "Check PowerShell conditional", + }, + capture(requests), ) const bashReq = requests.find((r) => r.permission === "bash") expect(bashReq).toBeDefined() expect(bashReq!.patterns).toContain("Write-Host foo") expect(bashReq!.patterns).toContain("Write-Host bar") expect(bashReq!.always).toContain("Write-Host *") - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `uses PowerShell cmdlet prefixes for always-allow prompts [${item.label}]`, - withShell(item, async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initShell() - const err = new Error("stop after permission") - const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( + it.live(`uses PowerShell cmdlet prefixes for always-allow prompts [${item.label}]`, () => + withShell( + item, + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const err = new Error("stop after permission") + const requests: Array> = [] + expect( + yield* fail( { command: "Remove-Item -Recurse tmp", description: "Remove a temp directory", }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) - const bashReq = requests.find((r) => r.permission === "bash") - expect(bashReq).toBeDefined() - expect(bashReq!.always).toContain("Remove-Item *") - expect(bashReq!.always).not.toContain("Remove-Item -Recurse *") - }, - }) - }), + ).toMatchObject({ message: err.message }) + const bashReq = requests.find((r) => r.permission === "bash") + expect(bashReq).toBeDefined() + expect(bashReq!.always).toContain("Remove-Item *") + expect(bashReq!.always).not.toContain("Remove-Item -Recurse *") + }), + ) + }), + ), ) } - each("asks for external_directory permission for wildcard external paths", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + each("asks for external_directory permission for wildcard external paths", () => + runIn( + projectRoot, + Effect.gen(function* () { const err = new Error("stop after permission") const requests: Array> = [] const file = process.platform === "win32" ? `${process.env.WINDIR!.replaceAll("\\", "/")}/*` : "/etc/*" const want = process.platform === "win32" ? glob(path.join(process.env.WINDIR!, "*")) : "/etc/*" - await expect( - Effect.runPromise( - bash.execute( - { - command: `cat ${file}`, - description: "Read wildcard path", - }, - capture(requests, err), - ), + expect( + yield* fail( + { + command: `cat ${file}`, + description: "Read wildcard path", + }, + capture(requests, err), ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) const extDirReq = requests.find((r) => r.permission === "external_directory") expect(extDirReq).toBeDefined() expect(extDirReq!.patterns).toContain(want) - }, - }) - }) + }), + ), + ) if (process.platform === "win32") { if (bash) { - test( - "asks for nested bash command permissions [bash]", - withShell({ label: "bash", shell: bash }, async () => { - await using outerTmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "outside.txt"), "x") - }, - }) - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() - const file = path.join(outerTmp.path, "outside.txt").replaceAll("\\", "/") - const requests: Array> = [] - await Effect.runPromise( - bash.execute( + it.live("asks for nested bash command permissions [bash]", () => + withShell( + { label: "bash", shell: bash }, + Effect.gen(function* () { + const outerTmp = yield* tmpdirScoped() + yield* Effect.promise(() => Bun.write(path.join(outerTmp, "outside.txt"), "x")) + yield* runIn( + projectRoot, + Effect.gen(function* () { + const file = path.join(outerTmp, "outside.txt").replaceAll("\\", "/") + const requests: Array> = [] + yield* run( { command: `echo $(cat "${file}")`, description: "Read nested bash file", }, capture(requests), - ), - ) - const extDirReq = requests.find((r) => r.permission === "external_directory") - const bashReq = requests.find((r) => r.permission === "bash") - expect(extDirReq).toBeDefined() - expect(extDirReq!.patterns).toContain(glob(path.join(outerTmp.path, "*"))) - expect(bashReq).toBeDefined() - expect(bashReq!.patterns).toContain(`cat "${file}"`) - }, - }) - }), + ) + const extDirReq = requests.find((r) => r.permission === "external_directory") + const bashReq = requests.find((r) => r.permission === "bash") + expect(extDirReq).toBeDefined() + expect(extDirReq!.patterns).toContain(glob(path.join(outerTmp, "*"))) + expect(bashReq).toBeDefined() + expect(bashReq!.patterns).toContain(`cat "${file}"`) + }), + ) + }), + ), ) } - } - if (process.platform === "win32") { for (const item of ps) { - test( - `asks for external_directory permission for PowerShell paths after switches [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live(`asks for external_directory permission for PowerShell paths after switches [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const err = new Error("stop after permission") const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( - { - command: `Copy-Item -PassThru "${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini" ./out`, - description: "Copy Windows ini", - }, - capture(requests, err), - ), + expect( + yield* fail( + { + command: `Copy-Item -PassThru "${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini" ./out`, + description: "Copy Windows ini", + }, + capture(requests, err), ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) const extDirReq = requests.find((r) => r.permission === "external_directory") expect(extDirReq).toBeDefined() expect(extDirReq!.patterns).toContain(glob(path.join(process.env.WINDIR!, "*"))) - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `asks for nested PowerShell command permissions [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live(`asks for nested PowerShell command permissions [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const requests: Array> = [] const file = `${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini` - await Effect.runPromise( - bash.execute( - { - command: `Write-Output $(Get-Content ${file})`, - description: "Read nested PowerShell file", - }, - capture(requests), - ), + yield* run( + { + command: `Write-Output $(Get-Content ${file})`, + description: "Read nested PowerShell file", + }, + capture(requests), ) const extDirReq = requests.find((r) => r.permission === "external_directory") const bashReq = requests.find((r) => r.permission === "bash") @@ -418,283 +417,266 @@ describe("tool.shell permissions", () => { expect(extDirReq!.patterns).toContain(glob(path.join(process.env.WINDIR!, "*"))) expect(bashReq).toBeDefined() expect(bashReq!.patterns).toContain(`Get-Content ${file}`) - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `asks for external_directory permission for drive-relative PowerShell paths [${item.label}]`, - withShell(item, async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initShell() - const err = new Error("stop after permission") - const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( + it.live(`asks for external_directory permission for drive-relative PowerShell paths [${item.label}]`, () => + withShell( + item, + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const err = new Error("stop after permission") + const requests: Array> = [] + expect( + yield* fail( { command: 'Get-Content "C:../outside.txt"', description: "Read drive-relative file", }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) - expect(requests[0]?.permission).toBe("external_directory") - if (requests[0]?.permission !== "external_directory") return - expect(requests[0].patterns).toContain(glob(path.join(path.dirname(tmp.path), "*"))) - }, - }) - }), + ).toMatchObject({ message: err.message }) + expect(requests[0]?.permission).toBe("external_directory") + if (requests[0]?.permission !== "external_directory") return + expect(requests[0].patterns).toContain(glob(path.join(path.dirname(tmp), "*"))) + }), + ) + }), + ), ) } for (const item of ps) { - test( - `asks for external_directory permission for $HOME PowerShell paths [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live(`asks for external_directory permission for $HOME PowerShell paths [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const err = new Error("stop after permission") const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( - { - command: 'Get-Content "$HOME/.ssh/config"', - description: "Read home config", - }, - capture(requests, err), - ), + expect( + yield* fail( + { + command: 'Get-Content "$HOME/.ssh/config"', + description: "Read home config", + }, + capture(requests, err), ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) expect(requests[0]?.permission).toBe("external_directory") if (requests[0]?.permission !== "external_directory") return expect(requests[0].patterns).toContain(glob(path.join(os.homedir(), ".ssh", "*"))) - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `asks for external_directory permission for $PWD PowerShell paths [${item.label}]`, - withShell(item, async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const err = new Error("stop after permission") - const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( + it.live(`asks for external_directory permission for $PWD PowerShell paths [${item.label}]`, () => + withShell( + item, + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const err = new Error("stop after permission") + const requests: Array> = [] + expect( + yield* fail( { command: 'Get-Content "$PWD/../outside.txt"', description: "Read pwd-relative file", }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) - expect(requests[0]?.permission).toBe("external_directory") - if (requests[0]?.permission !== "external_directory") return - expect(requests[0].patterns).toContain(glob(path.join(path.dirname(tmp.path), "*"))) - }, - }) - }), + ).toMatchObject({ message: err.message }) + expect(requests[0]?.permission).toBe("external_directory") + if (requests[0]?.permission !== "external_directory") return + expect(requests[0].patterns).toContain(glob(path.join(path.dirname(tmp), "*"))) + }), + ) + }), + ), ) } for (const item of ps) { - test( - `asks for external_directory permission for $PSHOME PowerShell paths [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initBash() + it.live(`asks for external_directory permission for $PSHOME PowerShell paths [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const err = new Error("stop after permission") const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( - { - command: 'Get-Content "$PSHOME/outside.txt"', - description: "Read pshome file", - }, - capture(requests, err), - ), + expect( + yield* fail( + { + command: 'Get-Content "$PSHOME/outside.txt"', + description: "Read pshome file", + }, + capture(requests, err), ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) expect(requests[0]?.permission).toBe("external_directory") if (requests[0]?.permission !== "external_directory") return expect(requests[0].patterns).toContain(glob(path.join(path.dirname(item.shell), "*"))) - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `asks for external_directory permission for missing PowerShell env paths [${item.label}]`, - withShell(item, async () => { - const key = "OPENCODE_TEST_MISSING" - const prev = process.env[key] - delete process.env[key] - try { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() - const err = new Error("stop after permission") - const requests: Array> = [] - const root = path.parse(process.env.WINDIR!).root.replace(/[\\/]+$/, "") - await expect( - Effect.runPromise( - bash.execute( + it.live(`asks for external_directory permission for missing PowerShell env paths [${item.label}]`, () => + withShell( + item, + Effect.acquireUseRelease( + Effect.sync(() => { + const key = "OPENCODE_TEST_MISSING" + const prev = process.env[key] + delete process.env[key] + return { key, prev } + }), + ({ key }) => + runIn( + projectRoot, + Effect.gen(function* () { + const err = new Error("stop after permission") + const requests: Array> = [] + const root = path.parse(process.env.WINDIR!).root.replace(/[\\/]+$/, "") + expect( + yield* fail( { command: `Get-Content -Path "${root}$env:${key}\\Windows\\win.ini"`, description: "Read Windows ini with missing env", }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect(extDirReq).toBeDefined() - expect(extDirReq!.patterns).toContain(glob(path.join(process.env.WINDIR!, "*"))) - }, - }) - } finally { - if (prev === undefined) delete process.env[key] - else process.env[key] = prev - } - }), + ).toMatchObject({ message: err.message }) + const extDirReq = requests.find((r) => r.permission === "external_directory") + expect(extDirReq).toBeDefined() + expect(extDirReq!.patterns).toContain(glob(path.join(process.env.WINDIR!, "*"))) + }), + ), + ({ key, prev }) => + Effect.sync(() => { + if (prev === undefined) delete process.env[key] + else process.env[key] = prev + }), + ), + ), ) } for (const item of ps) { - test( - `asks for external_directory permission for PowerShell env paths [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initBash() + it.live(`asks for external_directory permission for PowerShell env paths [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const requests: Array> = [] - await Effect.runPromise( - bash.execute( - { - command: "Get-Content $env:WINDIR/win.ini", - description: "Read Windows ini from env", - }, - capture(requests), - ), + yield* run( + { + command: "Get-Content $env:WINDIR/win.ini", + description: "Read Windows ini from env", + }, + capture(requests), ) const extDirReq = requests.find((r) => r.permission === "external_directory") expect(extDirReq).toBeDefined() expect(extDirReq!.patterns).toContain( Filesystem.normalizePathPattern(path.join(process.env.WINDIR!, "*")), ) - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `asks for external_directory permission for PowerShell FileSystem paths [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initBash() + it.live(`asks for external_directory permission for PowerShell FileSystem paths [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const err = new Error("stop after permission") const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( - { - command: `Get-Content -Path FileSystem::${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini`, - description: "Read Windows ini from FileSystem provider", - }, - capture(requests, err), - ), + expect( + yield* fail( + { + command: `Get-Content -Path FileSystem::${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini`, + description: "Read Windows ini from FileSystem provider", + }, + capture(requests, err), ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) expect(requests[0]?.permission).toBe("external_directory") if (requests[0]?.permission !== "external_directory") return expect(requests[0].patterns).toContain( Filesystem.normalizePathPattern(path.join(process.env.WINDIR!, "*")), ) - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `asks for external_directory permission for braced PowerShell env paths [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initBash() + it.live(`asks for external_directory permission for braced PowerShell env paths [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const err = new Error("stop after permission") const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( - { - command: "Get-Content ${env:WINDIR}/win.ini", - description: "Read Windows ini from braced env", - }, - capture(requests, err), - ), + expect( + yield* fail( + { + command: "Get-Content ${env:WINDIR}/win.ini", + description: "Read Windows ini from braced env", + }, + capture(requests, err), ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) expect(requests[0]?.permission).toBe("external_directory") if (requests[0]?.permission !== "external_directory") return expect(requests[0].patterns).toContain( Filesystem.normalizePathPattern(path.join(process.env.WINDIR!, "*")), ) - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `treats Set-Location like cd for permissions [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initBash() + it.live(`treats Set-Location like cd for permissions [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const requests: Array> = [] - await Effect.runPromise( - bash.execute( - { - command: "Set-Location C:/Windows", - description: "Change location", - }, - capture(requests), - ), + yield* run( + { + command: "Set-Location C:/Windows", + description: "Change location", + }, + capture(requests), ) const extDirReq = requests.find((r) => r.permission === "external_directory") const bashReq = requests.find((r) => r.permission === "bash") @@ -703,104 +685,96 @@ describe("tool.shell permissions", () => { Filesystem.normalizePathPattern(path.join(process.env.WINDIR!, "*")), ) expect(bashReq).toBeUndefined() - }, - }) - }), + }), + ), + ), ) } for (const item of ps) { - test( - `does not add nested PowerShell expressions to permission prompts [${item.label}]`, - withShell(item, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live(`does not add nested PowerShell expressions to permission prompts [${item.label}]`, () => + withShell( + item, + runIn( + projectRoot, + Effect.gen(function* () { const requests: Array> = [] - await Effect.runPromise( - bash.execute( - { - command: "Write-Output ('a' * 3)", - description: "Write repeated text", - }, - capture(requests), - ), + yield* run( + { + command: "Write-Output ('a' * 3)", + description: "Write repeated text", + }, + capture(requests), ) const bashReq = requests.find((r) => r.permission === "bash") expect(bashReq).toBeDefined() expect(bashReq!.patterns).not.toContain("a * 3") expect(bashReq!.always).not.toContain("a *") - }, - }) - }), + }), + ), + ), ) } } if (process.platform === "win32" && cmdShell) { - test( - "asks for external_directory permission for cmd file commands [cmd]", - withShell(cmdShell, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live("asks for external_directory permission for cmd file commands [cmd]", () => + withShell( + cmdShell, + runIn( + projectRoot, + Effect.gen(function* () { const requests: Array> = [] - await Effect.runPromise( - bash.execute( - { - command: `TYPE "${path.join(process.env.WINDIR!, "win.ini")}"`, - description: "Read Windows ini with cmd", - }, - capture(requests), - ), + yield* run( + { + command: `TYPE "${path.join(process.env.WINDIR!, "win.ini")}"`, + description: "Read Windows ini with cmd", + }, + capture(requests), ) const extDirReq = requests.find((r) => r.permission === "external_directory") expect(extDirReq).toBeDefined() expect(extDirReq!.patterns).toContain(Filesystem.normalizePathPattern(path.join(process.env.WINDIR!, "*"))) - }, - }) - }), + }), + ), + ), ) } - each("asks for external_directory permission when cd to parent", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const err = new Error("stop after permission") - const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( + each("asks for external_directory permission when cd to parent", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const err = new Error("stop after permission") + const requests: Array> = [] + expect( + yield* fail( { command: "cd ../", description: "Change to parent directory", }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect(extDirReq).toBeDefined() - }, - }) - }) + ).toMatchObject({ message: err.message }) + const extDirReq = requests.find((r) => r.permission === "external_directory") + expect(extDirReq).toBeDefined() + }), + ) + }), + ) - each("asks for external_directory permission when workdir is outside project", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const err = new Error("stop after permission") - const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( + each("asks for external_directory permission when workdir is outside project", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const err = new Error("stop after permission") + const requests: Array> = [] + expect( + yield* fail( { command: "echo ok", workdir: os.tmpdir(), @@ -808,31 +782,30 @@ describe("tool.shell permissions", () => { }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect(extDirReq).toBeDefined() - expect(extDirReq!.patterns).toContain(glob(path.join(os.tmpdir(), "*"))) - }, - }) - }) + ).toMatchObject({ message: err.message }) + const extDirReq = requests.find((r) => r.permission === "external_directory") + expect(extDirReq).toBeDefined() + expect(extDirReq!.patterns).toContain(glob(path.join(os.tmpdir(), "*"))) + }), + ) + }), + ) if (process.platform === "win32") { - test("normalizes external_directory workdir variants on Windows", async () => { - const err = new Error("stop after permission") - await using outerTmp = await tmpdir() - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const want = Filesystem.normalizePathPattern(path.join(outerTmp.path, "*")) + it.live("normalizes external_directory workdir variants on Windows", () => + Effect.gen(function* () { + const err = new Error("stop after permission") + const outerTmp = yield* tmpdirScoped() + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const want = Filesystem.normalizePathPattern(path.join(outerTmp, "*")) - for (const dir of forms(outerTmp.path)) { - const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( + for (const dir of forms(outerTmp)) { + const requests: Array> = [] + expect( + yield* fail( { command: "echo ok", workdir: dir, @@ -840,240 +813,224 @@ describe("tool.shell permissions", () => { }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect({ dir, patterns: extDirReq?.patterns, always: extDirReq?.always }).toEqual({ - dir, - patterns: [want], - always: [want], - }) - } - }, - }) - }) + const extDirReq = requests.find((r) => r.permission === "external_directory") + expect({ dir, patterns: extDirReq?.patterns, always: extDirReq?.always }).toEqual({ + dir, + patterns: [want], + always: [want], + }) + } + }), + ) + }), + ) if (bash) { - test( - "uses Git Bash /tmp semantics for external workdir", - withShell({ label: "bash", shell: bash }, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initBash() + it.live("uses Git Bash /tmp semantics for external workdir", () => + withShell( + { label: "bash", shell: bash }, + runIn( + projectRoot, + Effect.gen(function* () { const err = new Error("stop after permission") const requests: Array> = [] const want = glob(path.join(os.tmpdir(), "*")) - await expect( - Effect.runPromise( - bash.execute( - { - command: "echo ok", - workdir: "/tmp", - description: "Echo from Git Bash tmp", - }, - capture(requests, err), - ), + expect( + yield* fail( + { + command: "echo ok", + workdir: "/tmp", + description: "Echo from Git Bash tmp", + }, + capture(requests, err), ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) expect(requests[0]).toMatchObject({ permission: "external_directory", patterns: [want], always: [want], }) - }, - }) - }), + }), + ), + ), ) - test( - "uses Git Bash /tmp semantics for external file paths", - withShell({ label: "bash", shell: bash }, async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initBash() + it.live("uses Git Bash /tmp semantics for external file paths", () => + withShell( + { label: "bash", shell: bash }, + runIn( + projectRoot, + Effect.gen(function* () { const err = new Error("stop after permission") const requests: Array> = [] const want = glob(path.join(os.tmpdir(), "*")) - await expect( - Effect.runPromise( - bash.execute( - { - command: "cat /tmp/opencode-does-not-exist", - description: "Read Git Bash tmp file", - }, - capture(requests, err), - ), + expect( + yield* fail( + { + command: "cat /tmp/opencode-does-not-exist", + description: "Read Git Bash tmp file", + }, + capture(requests, err), ), - ).rejects.toThrow(err.message) + ).toMatchObject({ message: err.message }) expect(requests[0]).toMatchObject({ permission: "external_directory", patterns: [want], always: [want], }) - }, - }) - }), + }), + ), + ), ) } } - each("asks for external_directory permission when file arg is outside project", async () => { - await using outerTmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "outside.txt"), "x") - }, - }) - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const err = new Error("stop after permission") - const requests: Array> = [] - const filepath = path.join(outerTmp.path, "outside.txt") - await expect( - Effect.runPromise( - bash.execute( + each("asks for external_directory permission when file arg is outside project", () => + Effect.gen(function* () { + const outerTmp = yield* tmpdirScoped() + yield* Effect.promise(() => Bun.write(path.join(outerTmp, "outside.txt"), "x")) + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const err = new Error("stop after permission") + const requests: Array> = [] + const filepath = path.join(outerTmp, "outside.txt") + expect( + yield* fail( { command: `cat ${filepath}`, description: "Read external file", }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) - const extDirReq = requests.find((r) => r.permission === "external_directory") - const expected = glob(path.join(outerTmp.path, "*")) - expect(extDirReq).toBeDefined() - expect(extDirReq!.patterns).toContain(expected) - expect(extDirReq!.always).toContain(expected) - }, - }) - }) + ).toMatchObject({ message: err.message }) + const extDirReq = requests.find((r) => r.permission === "external_directory") + const expected = glob(path.join(outerTmp, "*")) + expect(extDirReq).toBeDefined() + expect(extDirReq!.patterns).toContain(expected) + expect(extDirReq!.always).toContain(expected) + }), + ) + }), + ) - each("does not ask for external_directory permission when rm inside project", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "tmpfile"), "x") - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const requests: Array> = [] - await Effect.runPromise( - bash.execute( + each("does not ask for external_directory permission when rm inside project", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* Effect.promise(() => Bun.write(path.join(tmp, "tmpfile"), "x")) + yield* runIn( + tmp, + Effect.gen(function* () { + const requests: Array> = [] + yield* run( { - command: `rm -rf ${path.join(tmp.path, "nested")}`, + command: `rm -rf ${path.join(tmp, "nested")}`, description: "Remove nested dir", }, capture(requests), - ), - ) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect(extDirReq).toBeUndefined() - }, - }) - }) + ) + const extDirReq = requests.find((r) => r.permission === "external_directory") + expect(extDirReq).toBeUndefined() + }), + ) + }), + ) - each("includes always patterns for auto-approval", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const requests: Array> = [] - await Effect.runPromise( - bash.execute( + each("includes always patterns for auto-approval", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const requests: Array> = [] + yield* run( { command: "git log --oneline -5", description: "Git log", }, capture(requests), - ), - ) - expect(requests.length).toBe(1) - expect(requests[0].always.length).toBeGreaterThan(0) - expect(requests[0].always.some((item) => item.endsWith("*"))).toBe(true) - }, - }) - }) + ) + expect(requests.length).toBe(1) + expect(requests[0].always.length).toBeGreaterThan(0) + expect(requests[0].always.some((item) => item.endsWith("*"))).toBe(true) + }), + ) + }), + ) - each("does not ask for bash permission when command is cd only", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initShell() - const requests: Array> = [] - await Effect.runPromise( - bash.execute( + each("does not ask for bash permission when command is cd only", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const requests: Array> = [] + yield* run( { command: "cd .", description: "Stay in current directory", }, capture(requests), - ), - ) - const bashReq = requests.find((r) => r.permission === "bash") - expect(bashReq).toBeUndefined() - }, - }) - }) + ) + const bashReq = requests.find((r) => r.permission === "bash") + expect(bashReq).toBeUndefined() + }), + ) + }), + ) - each("matches redirects in permission pattern", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initShell() - const err = new Error("stop after permission") - const requests: Array> = [] - await expect( - Effect.runPromise( - bash.execute( + each("matches redirects in permission pattern", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const err = new Error("stop after permission") + const requests: Array> = [] + expect( + yield* fail( { command: "echo test > output.txt", description: "Redirect test output" }, capture(requests, err), ), - ), - ).rejects.toThrow(err.message) - const bashReq = requests.find((r) => r.permission === "bash") - expect(bashReq).toBeDefined() - expect(bashReq!.patterns).toContain("echo test > output.txt") - }, - }) - }) + ).toMatchObject({ message: err.message }) + const bashReq = requests.find((r) => r.permission === "bash") + expect(bashReq).toBeDefined() + expect(bashReq!.patterns).toContain("echo test > output.txt") + }), + ) + }), + ) - each("always pattern has space before wildcard to not include different commands", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const bash = await initBash() - const requests: Array> = [] - await Effect.runPromise(bash.execute({ command: "ls -la", description: "List" }, capture(requests))) - const bashReq = requests.find((r) => r.permission === "bash") - expect(bashReq).toBeDefined() - expect(bashReq!.always[0]).toBe("ls *") - }, - }) - }) + each("always pattern has space before wildcard to not include different commands", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* runIn( + tmp, + Effect.gen(function* () { + const requests: Array> = [] + yield* run({ command: "ls -la", description: "List" }, capture(requests)) + const bashReq = requests.find((r) => r.permission === "bash") + expect(bashReq).toBeDefined() + expect(bashReq!.always[0]).toBe("ls *") + }), + ) + }), + ) }) describe("tool.shell abort", () => { - test("preserves output when aborted", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() - const controller = new AbortController() - const collected: string[] = [] - const res = await Effect.runPromise( - bash.execute( + it.live( + "preserves output when aborted", + () => + runIn( + projectRoot, + Effect.gen(function* () { + const controller = new AbortController() + const collected: string[] = [] + const res = yield* run( { command: `echo before && sleep 30`, description: "Long running command", @@ -1090,198 +1047,158 @@ describe("tool.shell abort", () => { } }), }, - ), - ) - expect(res.output).toContain("before") - expect(res.output).toContain("User aborted the command") - expect(collected.length).toBeGreaterThan(0) - }, - }) - }, 15_000) + ) + expect(res.output).toContain("before") + expect(res.output).toContain("User aborted the command") + expect(collected.length).toBeGreaterThan(0) + }), + ), + 15_000, + ) - test("terminates command on timeout", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() - const result = await Effect.runPromise( - bash.execute( - { - command: `echo started && sleep 60`, - description: "Timeout test", - timeout: 500, - }, - ctx, - ), - ) - expect(result.output).toContain("started") - expect(result.output).toContain("shell tool terminated command after exceeding timeout") - expect(result.output).toContain("retry with a larger timeout value in milliseconds") - }, - }) - }, 15_000) + it.live( + "terminates command on timeout", + () => + runIn( + projectRoot, + Effect.gen(function* () { + const result = yield* run({ + command: `echo started && sleep 60`, + description: "Timeout test", + timeout: 500, + }) + expect(result.output).toContain("started") + expect(result.output).toContain("shell tool terminated command after exceeding timeout") + expect(result.output).toContain("retry with a larger timeout value in milliseconds") + }), + ), + 15_000, + ) - test.skipIf(process.platform === "win32")("captures stderr in output", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() - const result = await Effect.runPromise( - bash.execute( - { - command: `echo stdout_msg && echo stderr_msg >&2`, - description: "Stderr test", - }, - ctx, - ), - ) - expect(result.output).toContain("stdout_msg") - expect(result.output).toContain("stderr_msg") - expect(result.metadata.exit).toBe(0) - }, - }) - }) + if (process.platform !== "win32") { + it.live("captures stderr in output", () => + runIn( + projectRoot, + Effect.gen(function* () { + const result = yield* run({ + command: `echo stdout_msg && echo stderr_msg >&2`, + description: "Stderr test", + }) + expect(result.output).toContain("stdout_msg") + expect(result.output).toContain("stderr_msg") + expect(result.metadata.exit).toBe(0) + }), + ), + ) + } - test("returns non-zero exit code", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() - const result = await Effect.runPromise( - bash.execute( - { - command: `exit 42`, - description: "Non-zero exit", - }, - ctx, - ), - ) + it.live("returns non-zero exit code", () => + runIn( + projectRoot, + Effect.gen(function* () { + const result = yield* run({ + command: `exit 42`, + description: "Non-zero exit", + }) expect(result.metadata.exit).toBe(42) - }, - }) - }) + }), + ), + ) - test("streams metadata updates progressively", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initBash() + it.live("streams metadata updates progressively", () => + runIn( + projectRoot, + Effect.gen(function* () { const updates: string[] = [] - const result = await Effect.runPromise( - bash.execute( - { - command: `echo first && sleep 0.1 && echo second`, - description: "Streaming test", - }, - { - ...ctx, - metadata: (input) => - Effect.sync(() => { - const output = (input.metadata as { output?: string })?.output - if (output) updates.push(output) - }), - }, - ), + const result = yield* run( + { + command: `echo first && sleep 0.1 && echo second`, + description: "Streaming test", + }, + { + ...ctx, + metadata: (input) => + Effect.sync(() => { + const output = (input.metadata as { output?: string })?.output + if (output) updates.push(output) + }), + }, ) expect(result.output).toContain("first") expect(result.output).toContain("second") expect(updates.length).toBeGreaterThan(1) - }, - }) - }) + }), + ), + ) }) describe("tool.shell truncation", () => { - test("truncates output exceeding line limit", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live("truncates output exceeding line limit", () => + runIn( + projectRoot, + Effect.gen(function* () { const lineCount = Truncate.MAX_LINES + 500 - const result = await Effect.runPromise( - bash.execute( - { - command: fill("lines", lineCount), - description: "Generate lines exceeding limit", - }, - ctx, - ), - ) + const result = yield* run({ + command: fill("lines", lineCount), + description: "Generate lines exceeding limit", + }) mustTruncate(result) expect(result.output).toMatch(/\.\.\.output truncated\.\.\./) expect(result.output).toMatch(/Full output saved to:\s+\S+/) - }, - }) - }) + }), + ), + ) - test("truncates output exceeding byte limit", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live("truncates output exceeding byte limit", () => + runIn( + projectRoot, + Effect.gen(function* () { const byteCount = Truncate.MAX_BYTES + 10000 - const result = await Effect.runPromise( - bash.execute( - { - command: fill("bytes", byteCount), - description: "Generate bytes exceeding limit", - }, - ctx, - ), - ) + const result = yield* run({ + command: fill("bytes", byteCount), + description: "Generate bytes exceeding limit", + }) mustTruncate(result) expect(result.output).toMatch(/\.\.\.output truncated\.\.\./) expect(result.output).toMatch(/Full output saved to:\s+\S+/) - }, - }) - }) + }), + ), + ) - test("does not truncate small output", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() - const result = await Effect.runPromise( - bash.execute( - { - command: "echo hello", - description: "Echo hello", - }, - ctx, - ), - ) + it.live("does not truncate small output", () => + runIn( + projectRoot, + Effect.gen(function* () { + const result = yield* run({ + command: fill("lines", 1), + description: "Generate one line", + }) expect((result.metadata as { truncated?: boolean }).truncated).toBe(false) - expect(result.output).toContain("hello") - }, - }) - }) + expect(result.output).toContain("1") + }), + ), + ) - test("full output is saved to file when truncated", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const bash = await initShell() + it.live("full output is saved to file when truncated", () => + runIn( + projectRoot, + Effect.gen(function* () { const lineCount = Truncate.MAX_LINES + 100 - const result = await Effect.runPromise( - bash.execute( - { - command: fill("lines", lineCount), - description: "Generate lines for file check", - }, - ctx, - ), - ) + const result = yield* run({ + command: fill("lines", lineCount), + description: "Generate lines for file check", + }) mustTruncate(result) const filepath = (result.metadata as { outputPath?: string }).outputPath expect(filepath).toBeTruthy() - const saved = await Filesystem.readText(filepath!) + const saved = yield* Effect.promise(() => Filesystem.readText(filepath!)) const lines = saved.trim().split(/\r?\n/) expect(lines.length).toBe(lineCount) expect(lines[0]).toBe("1") expect(lines[lineCount - 1]).toBe(String(lineCount)) - }, - }) - }) + }), + ), + ) }) From 0fd0facc442b1c9dd10264437d3439207c18653c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 13:31:03 +0000 Subject: [PATCH 096/378] chore: generate --- packages/opencode/test/tool/shell.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/tool/shell.test.ts b/packages/opencode/test/tool/shell.test.ts index 4b4cc8c88..6ce0e5c08 100644 --- a/packages/opencode/test/tool/shell.test.ts +++ b/packages/opencode/test/tool/shell.test.ts @@ -27,7 +27,9 @@ const shellLayer = Layer.mergeAll( Agent.defaultLayer, ) const it = testEffect(shellLayer) -type ShellTestServices = (typeof shellLayer extends Layer.Layer ? ROut : never) | Scope.Scope +type ShellTestServices = + | (typeof shellLayer extends Layer.Layer ? ROut : never) + | Scope.Scope const initShell = Effect.fn("ShellToolTest.init")(function* () { const info = yield* ShellTool From 04aafe2bfcd42ce6fb9b8f118225d92a93e858d1 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 10:19:49 -0400 Subject: [PATCH 097/378] test(provider): migrate more config-backed cases (#27067) --- .../opencode/test/provider/provider.test.ts | 90 ++++++++----------- 1 file changed, 39 insertions(+), 51 deletions(-) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index ea65c90c4..2270418be 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -582,64 +582,52 @@ it.instance( }, ) -test("model options are merged from existing model", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - provider: { - anthropic: { - models: { - "claude-sonnet-4-20250514": { - options: { - customOption: "custom-value", - }, - }, +it.instance( + "model options are merged from existing model", + Effect.gen(function* () { + const providers = yield* Provider.Service.use((provider) => provider.list()) + const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] + expect(model.options.customOption).toBe("custom-value") + }), + { + config: { + provider: { + anthropic: { + options: { + apiKey: "test-api-key", + }, + models: { + "claude-sonnet-4-20250514": { + options: { + customOption: "custom-value", }, }, }, - }), - ) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() - const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"] - expect(model.options.customOption).toBe("custom-value") + }, + }, }, - }) -}) + }, +) -test("provider removed when all models filtered out", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - provider: { - anthropic: { - whitelist: ["nonexistent-model"], - }, +it.instance( + "provider removed when all models filtered out", + Effect.gen(function* () { + const providers = yield* Provider.Service.use((provider) => provider.list()) + expect(providers[ProviderID.anthropic]).toBeUndefined() + }), + { + config: { + provider: { + anthropic: { + options: { + apiKey: "test-api-key", }, - }), - ) - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - set("ANTHROPIC_API_KEY", "test-api-key") - const providers = await list() - expect(providers[ProviderID.anthropic]).toBeUndefined() + whitelist: ["nonexistent-model"], + }, + }, }, - }) -}) + }, +) test("closest finds model by partial match", async () => { await using tmp = await tmpdir({ From 257fcafc8302d7a6f31d8d5d0e733bf9a5ff9181 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 11:52:31 -0400 Subject: [PATCH 098/378] test(tool): migrate edit concurrency test (#26983) --- packages/opencode/test/tool/edit.test.ts | 118 +++++++++-------------- 1 file changed, 47 insertions(+), 71 deletions(-) diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index 572fcd9aa..6a1682826 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -1,10 +1,9 @@ -import { afterAll, afterEach, describe, test, expect } from "bun:test" +import { afterEach, describe, expect } from "bun:test" import path from "path" import fs from "fs/promises" -import { Cause, Deferred, Effect, Exit, Layer, ManagedRuntime } from "effect" +import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect" import { EditTool } from "../../src/tool/edit" -import { WithInstance } from "../../src/project/with-instance" -import { disposeAllInstances, TestInstance, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { LSP } from "@/lsp/lsp" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Format } from "../../src/format" @@ -42,20 +41,6 @@ const layer = Layer.mergeAll( const it = testEffect(layer) -const runtime = ManagedRuntime.make(layer) - -afterAll(async () => { - await runtime.dispose() -}) - -const resolve = () => - runtime.runPromise( - Effect.gen(function* () { - const info = yield* EditTool - return yield* info.init() - }), - ) - const init = Effect.fn("EditToolTest.init")(function* () { const info = yield* EditTool return yield* info.init() @@ -500,58 +485,49 @@ describe("tool.edit", () => { }) describe("concurrent editing", () => { - test("preserves concurrent edits to different sections of the same file", async () => { - await using tmp = await tmpdir() - const filepath = path.join(tmp.path, "file.txt") - await fs.writeFile(filepath, "top = 0\nmiddle = keep\nbottom = 0\n", "utf-8") - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const edit = await resolve() - let asks = 0 - const firstAsk = Promise.withResolvers() - const delayedCtx = { - ...ctx, - ask: () => - Effect.gen(function* () { - asks++ - if (asks !== 1) return - firstAsk.resolve() - yield* Effect.promise(() => Bun.sleep(50)) - }), - } - - const promise1 = Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "top = 0", - newString: "top = 1", - }, - delayedCtx, - ), - ) - - await firstAsk.promise - - const promise2 = Effect.runPromise( - edit.execute( - { - filePath: filepath, - oldString: "bottom = 0", - newString: "bottom = 2", - }, - delayedCtx, - ), - ) - - const results = await Promise.allSettled([promise1, promise2]) - expect(results[0]?.status).toBe("fulfilled") - expect(results[1]?.status).toBe("fulfilled") - expect(await fs.readFile(filepath, "utf-8")).toBe("top = 1\nmiddle = keep\nbottom = 2\n") - }, - }) - }) + it.instance("preserves concurrent edits to different sections of the same file", () => + Effect.gen(function* () { + const test = yield* TestInstance + const filepath = path.join(test.directory, "file.txt") + yield* put(filepath, "top = 0\nmiddle = keep\nbottom = 0\n") + + const firstAsk = yield* Deferred.make() + let asks = 0 + const delayedCtx = { + ...ctx, + ask: () => + Effect.gen(function* () { + asks++ + if (asks !== 1) return + yield* Deferred.succeed(firstAsk, undefined) + yield* Effect.promise(() => Bun.sleep(50)) + }), + } + + const first = yield* run( + { + filePath: filepath, + oldString: "top = 0", + newString: "top = 1", + }, + delayedCtx, + ).pipe(Effect.forkScoped) + + yield* Deferred.await(firstAsk) + yield* Effect.all([ + Fiber.join(first), + run( + { + filePath: filepath, + oldString: "bottom = 0", + newString: "bottom = 2", + }, + delayedCtx, + ), + ]) + + expect(yield* load(filepath)).toBe("top = 1\nmiddle = keep\nbottom = 2\n") + }), + ) }) }) From c7d8b0d56562209d69ef6a20424a4681827766bd Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 12:04:28 -0400 Subject: [PATCH 099/378] Delete named schema error wrapper (#27066) --- packages/opencode/src/provider/auth.ts | 10 ++++----- packages/opencode/src/provider/provider.ts | 6 +++--- .../opencode/src/session/message-error.ts | 14 +++++++++++++ packages/opencode/src/session/message-v2.ts | 21 +++++++------------ packages/opencode/src/session/message.ts | 15 ++++--------- .../opencode/src/util/named-schema-error.ts | 9 -------- packages/opencode/test/util/error.test.ts | 9 ++++---- 7 files changed, 38 insertions(+), 46 deletions(-) create mode 100644 packages/opencode/src/session/message-error.ts delete mode 100644 packages/opencode/src/util/named-schema-error.ts diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 42b94ffcc..ba2a8c744 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,7 +1,7 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" import { Auth } from "@/auth" import { InstanceState } from "@/effect/instance-state" -import { namedSchemaError } from "@/util/named-schema-error" +import { NamedError } from "@opencode-ai/core/util/error" import { optionalOmitUndefined } from "@opencode-ai/core/schema" import { Plugin } from "../plugin" import { ProviderID } from "./schema" @@ -64,13 +64,13 @@ export const CallbackInput = Schema.Struct({ }) export type CallbackInput = Schema.Schema.Type -export const OauthMissing = namedSchemaError("ProviderAuthOauthMissing", { providerID: ProviderID }) +export const OauthMissing = NamedError.create("ProviderAuthOauthMissing", { providerID: ProviderID }) -export const OauthCodeMissing = namedSchemaError("ProviderAuthOauthCodeMissing", { providerID: ProviderID }) +export const OauthCodeMissing = NamedError.create("ProviderAuthOauthCodeMissing", { providerID: ProviderID }) -export const OauthCallbackFailed = namedSchemaError("ProviderAuthOauthCallbackFailed", {}) +export const OauthCallbackFailed = NamedError.create("ProviderAuthOauthCallbackFailed", {}) -export const ValidationFailed = namedSchemaError("ProviderAuthValidationFailed", { +export const ValidationFailed = NamedError.create("ProviderAuthValidationFailed", { field: Schema.String, message: Schema.String, }) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 236f14de7..f381e848d 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -13,7 +13,7 @@ import { Auth } from "../auth" import { Env } from "../env" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { Flag } from "@opencode-ai/core/flag/flag" -import { namedSchemaError } from "@/util/named-schema-error" +import { NamedError } from "@opencode-ai/core/util/error" import { iife } from "@/util/iife" import { Global } from "@opencode-ai/core/global" import path from "path" @@ -1749,13 +1749,13 @@ export function parseModel(model: string) { } } -export const ModelNotFoundError = namedSchemaError("ProviderModelNotFoundError", { +export const ModelNotFoundError = NamedError.create("ProviderModelNotFoundError", { providerID: ProviderID, modelID: ModelID, suggestions: Schema.optional(Schema.Array(Schema.String)), }) -export const InitError = namedSchemaError("ProviderInitError", { +export const InitError = NamedError.create("ProviderInitError", { providerID: ProviderID, }) diff --git a/packages/opencode/src/session/message-error.ts b/packages/opencode/src/session/message-error.ts new file mode 100644 index 000000000..bf40d45be --- /dev/null +++ b/packages/opencode/src/session/message-error.ts @@ -0,0 +1,14 @@ +import { Schema } from "effect" +import { NamedError } from "@opencode-ai/core/util/error" + +export const OutputLengthError = NamedError.create("MessageOutputLengthError", {}) + +export const AuthError = NamedError.create("ProviderAuthError", { + providerID: Schema.String, + message: Schema.String, +}) + +export const Shared = [AuthError.EffectSchema, NamedError.Unknown.EffectSchema, OutputLengthError.EffectSchema] as const +export const SharedSchema = Schema.Union(Shared) + +export * as MessageError from "./message-error" diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index f797e2dc3..626261d0f 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -23,8 +23,10 @@ import type { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "@/provider/schema" import { Effect, Schema, Types } from "effect" import { NonNegativeInt } from "@opencode-ai/core/schema" -import { namedSchemaError } from "@/util/named-schema-error" import * as EffectLogger from "@opencode-ai/core/effect/logger" +import { MessageError } from "./message-error" +import { AuthError, OutputLengthError } from "./message-error" +export { AuthError, OutputLengthError } from "./message-error" /** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */ interface FetchDecompressionError extends Error { @@ -36,17 +38,12 @@ interface FetchDecompressionError extends Error { export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached media from tool result:" export { isMedia } -export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}) -export const AbortedError = namedSchemaError("MessageAbortedError", { message: Schema.String }) -export const StructuredOutputError = namedSchemaError("StructuredOutputError", { +export const AbortedError = NamedError.create("MessageAbortedError", { message: Schema.String }) +export const StructuredOutputError = NamedError.create("StructuredOutputError", { message: Schema.String, retries: NonNegativeInt, }) -export const AuthError = namedSchemaError("ProviderAuthError", { - providerID: Schema.String, - message: Schema.String, -}) -export const APIError = namedSchemaError("APIError", { +export const APIError = NamedError.create("APIError", { message: Schema.String, statusCode: Schema.optional(NonNegativeInt), isRetryable: Schema.Boolean, @@ -55,7 +52,7 @@ export const APIError = namedSchemaError("APIError", { metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), }) export type APIError = Schema.Schema.Type -export const ContextOverflowError = namedSchemaError("ContextOverflowError", { +export const ContextOverflowError = NamedError.create("ContextOverflowError", { message: Schema.String, responseBody: Schema.optional(Schema.String), }) @@ -381,9 +378,7 @@ export type Part = | CompactionPart const AssistantErrorSchema = Schema.Union([ - AuthError.EffectSchema, - NamedError.Unknown.EffectSchema, - OutputLengthError.EffectSchema, + ...MessageError.Shared, AbortedError.EffectSchema, StructuredOutputError.EffectSchema, ContextOverflowError.EffectSchema, diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts index 6a859ffaa..39c842f94 100644 --- a/packages/opencode/src/session/message.ts +++ b/packages/opencode/src/session/message.ts @@ -2,14 +2,9 @@ import { Schema } from "effect" import { SessionID } from "./schema" import { ModelID, ProviderID } from "../provider/schema" import { NonNegativeInt } from "@opencode-ai/core/schema" -import { namedSchemaError } from "@/util/named-schema-error" -import { NamedError } from "@opencode-ai/core/util/error" - -export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}) -export const AuthError = namedSchemaError("ProviderAuthError", { - providerID: Schema.String, - message: Schema.String, -}) +import { MessageError } from "./message-error" +import { AuthError, OutputLengthError } from "./message-error" +export { AuthError, OutputLengthError } from "./message-error" export const ToolCall = Schema.Struct({ state: Schema.Literal("call"), @@ -105,9 +100,7 @@ export const Info = Schema.Struct({ created: NonNegativeInt, completed: Schema.optional(NonNegativeInt), }), - error: Schema.optional( - Schema.Union([AuthError.EffectSchema, NamedError.Unknown.EffectSchema, OutputLengthError.EffectSchema]), - ), + error: Schema.optional(MessageError.SharedSchema), sessionID: SessionID, tool: Schema.Record( Schema.String, diff --git a/packages/opencode/src/util/named-schema-error.ts b/packages/opencode/src/util/named-schema-error.ts deleted file mode 100644 index cc02c3731..000000000 --- a/packages/opencode/src/util/named-schema-error.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Schema } from "effect" -import { NamedError } from "@opencode-ai/core/util/error" - -/** - * Create a Schema-backed NamedError-shaped class. - */ -export function namedSchemaError(tag: Tag, fields: Fields) { - return NamedError.create(tag, fields) -} diff --git a/packages/opencode/test/util/error.test.ts b/packages/opencode/test/util/error.test.ts index bc966133a..8d077b1f2 100644 --- a/packages/opencode/test/util/error.test.ts +++ b/packages/opencode/test/util/error.test.ts @@ -2,8 +2,8 @@ import { describe, expect, test } from "bun:test" import { Schema } from "effect" import { NamedError } from "@opencode-ai/core/util/error" import { errorData, errorFormat, errorMessage } from "../../src/util/error" -import { namedSchemaError } from "../../src/util/named-schema-error" import { UI } from "../../src/cli/ui" +import { MessageError } from "../../src/session/message-error" describe("util.error", () => { test("formats native Error instances", () => { @@ -53,12 +53,11 @@ describe("util.error", () => { expect(String(data.formatted)).toContain("ResolveMessage") }) - test("named schema errors are real NamedError instances", () => { - const ExampleError = namedSchemaError("ExampleError", { message: Schema.String }) - const error = new ExampleError({ message: "boom" }) + test("schema-backed named errors are real NamedError instances", () => { + const error = new MessageError.AuthError({ providerID: "anthropic", message: "boom" }) expect(error).toBeInstanceOf(NamedError) - expect(error.toObject()).toEqual({ name: "ExampleError", data: { message: "boom" } }) + expect(error.toObject()).toEqual({ name: "ProviderAuthError", data: { providerID: "anthropic", message: "boom" } }) }) test("void named errors accept JSON without data", () => { From 23f8b3eb3e55a0780e4d7bccc4d99825c2c3acc1 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 12 May 2026 11:31:18 -0500 Subject: [PATCH 100/378] fix: annotate Effect log metadata (#27093) --- packages/opencode/src/data-migration.ts | 2 +- packages/opencode/src/project/bootstrap.ts | 2 +- packages/opencode/src/project/instance-store.ts | 4 +++- packages/opencode/src/provider/models.ts | 2 +- packages/opencode/src/reference/reference.ts | 4 +++- .../src/server/routes/instance/httpapi/handlers/session.ts | 4 +++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/data-migration.ts b/packages/opencode/src/data-migration.ts index 53e3196b7..331e0f750 100644 --- a/packages/opencode/src/data-migration.ts +++ b/packages/opencode/src/data-migration.ts @@ -145,7 +145,7 @@ export const layer = Layer.effect( ) } }).pipe( - Effect.tapCause((cause) => Effect.logError("failed to run data migrations", { cause })), + Effect.tapCause((cause) => Effect.logError("failed to run data migrations").pipe(Effect.annotateLogs("cause", cause))), Effect.ignore, Effect.forkScoped, ) diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts index 6103a9efb..a7e67d45e 100644 --- a/packages/opencode/src/project/bootstrap.ts +++ b/packages/opencode/src/project/bootstrap.ts @@ -37,7 +37,7 @@ export const layer = Layer.effect( const run = Effect.gen(function* () { const ctx = yield* InstanceState.context - yield* Effect.logInfo("bootstrapping", { directory: ctx.directory }) + yield* Effect.logInfo("bootstrapping").pipe(Effect.annotateLogs("directory", ctx.directory)) // everything depends on config so eager load it for nice traces yield* config.get() // Plugin can mutate config so it has to be initialized before anything else. diff --git a/packages/opencode/src/project/instance-store.ts b/packages/opencode/src/project/instance-store.ts index 9707305f9..faa56668a 100644 --- a/packages/opencode/src/project/instance-store.ts +++ b/packages/opencode/src/project/instance-store.ts @@ -156,7 +156,9 @@ export const layer: Layer.Layer Effect.logError("Failed to fetch models.dev", { cause })), + Effect.tapCause((cause) => Effect.logError("Failed to fetch models.dev").pipe(Effect.annotateLogs("cause", cause))), Effect.ignore, ) }) diff --git a/packages/opencode/src/reference/reference.ts b/packages/opencode/src/reference/reference.ts index 09e0a825d..748c3b238 100644 --- a/packages/opencode/src/reference/reference.ts +++ b/packages/opencode/src/reference/reference.ts @@ -169,7 +169,9 @@ export const layer = Layer.effect( ).pipe( Effect.asVoid, Effect.catchCause((cause) => - Effect.logWarning("failed to materialize reference repository", { name: reference.name, cause }), + Effect.logWarning("failed to materialize reference repository").pipe( + Effect.annotateLogs({ name: reference.name, cause }), + ), ), ), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 99645f3da..236eee13f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -299,7 +299,9 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", yield* promptSvc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID }).pipe( Effect.catchCause((cause) => Effect.gen(function* () { - yield* Effect.logError("prompt_async failed", { sessionID: ctx.params.sessionID, cause }) + yield* Effect.logError("prompt_async failed").pipe( + Effect.annotateLogs({ sessionID: ctx.params.sessionID, cause }), + ) yield* bus.publish(Session.Event.Error, { sessionID: ctx.params.sessionID, error: new NamedError.Unknown({ message: Cause.pretty(cause) }).toObject(), From 30e3fa1de966e828db43a8f00f265beaad92a455 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 16:33:06 +0000 Subject: [PATCH 101/378] chore: generate --- packages/opencode/src/data-migration.ts | 4 +++- packages/opencode/src/provider/models.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/data-migration.ts b/packages/opencode/src/data-migration.ts index 331e0f750..bec4c9c8a 100644 --- a/packages/opencode/src/data-migration.ts +++ b/packages/opencode/src/data-migration.ts @@ -145,7 +145,9 @@ export const layer = Layer.effect( ) } }).pipe( - Effect.tapCause((cause) => Effect.logError("failed to run data migrations").pipe(Effect.annotateLogs("cause", cause))), + Effect.tapCause((cause) => + Effect.logError("failed to run data migrations").pipe(Effect.annotateLogs("cause", cause)), + ), Effect.ignore, Effect.forkScoped, ) diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index d3d04b552..e9d2bac1a 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -177,7 +177,9 @@ export const layer: Layer.Layer Effect.logError("Failed to fetch models.dev").pipe(Effect.annotateLogs("cause", cause))), + Effect.tapCause((cause) => + Effect.logError("Failed to fetch models.dev").pipe(Effect.annotateLogs("cause", cause)), + ), Effect.ignore, ) }) From d658e1e3506f55093ecb894e53fa9ac3cb7cd98a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 12:38:27 -0400 Subject: [PATCH 102/378] Remove local MCP Zod schema (#27095) --- packages/opencode/src/mcp/index.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 992825dd6..832811b28 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -6,6 +6,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js" import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" import { CallToolResultSchema, + ListToolsResultSchema, ToolSchema, type Tool as MCPToolDef, ToolListChangedNotificationSchema, @@ -14,7 +15,6 @@ import { Config } from "@/config/config" import { ConfigMCP } from "../config/mcp" import * as Log from "@opencode-ai/core/util/log" import { NamedError } from "@opencode-ai/core/util/error" -import z from "zod/v4" import { Installation } from "../installation" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { withTimeout } from "@/util/timeout" @@ -35,13 +35,8 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" const log = Log.create({ service: "mcp" }) const DEFAULT_TIMEOUT = 30_000 -const TolerantToolSchema = ToolSchema.extend({ - outputSchema: z.unknown().optional(), -}) - -const TolerantListToolsResultSchema = z.looseObject({ - tools: z.array(TolerantToolSchema), - nextCursor: z.string().optional(), +const TolerantListToolsResultSchema = ListToolsResultSchema.extend({ + tools: ToolSchema.omit({ outputSchema: true }).array(), }) export const Resource = Schema.Struct({ @@ -137,7 +132,10 @@ function listTools(key: string, client: MCPClient, timeout: number) { log.warn("failed to validate MCP tool output schemas, retrying without output schema validation", { key, error }) return Effect.tryPromise({ - try: () => client.request({ method: "tools/list" }, TolerantListToolsResultSchema, { timeout }), + try: () => + client.request({ method: "tools/list" }, TolerantListToolsResultSchema, { + timeout, + }), catch: (err) => (err instanceof Error ? err : new Error(String(err))), }).pipe( Effect.map((result) => From 3dc2c1d81c3ac6a0e508ddf3cf918e76888faf08 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 12 May 2026 22:10:28 +0530 Subject: [PATCH 103/378] fix(session): preserve usage update timestamps (#27094) --- packages/opencode/src/data-migration.ts | 1 + packages/opencode/src/session/projectors.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/data-migration.ts b/packages/opencode/src/data-migration.ts index bec4c9c8a..b6956032a 100644 --- a/packages/opencode/src/data-migration.ts +++ b/packages/opencode/src/data-migration.ts @@ -94,6 +94,7 @@ export const layer = Layer.effect( tokens_reasoning: value.tokens.reasoning, tokens_cache_read: value.tokens.cache.read, tokens_cache_write: value.tokens.cache.write, + time_updated: sql`${SessionTable.time_updated}`, }) .where(eq(SessionTable.id, sessionID)) .run() diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 8b5cc2bdc..3dd848c5b 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -38,6 +38,7 @@ function applyUsage(db: TxOrDb, sessionID: Session.Info["id"], value: Usage, sig tokens_reasoning: sql`${SessionTable.tokens_reasoning} + ${value.tokens.reasoning * sign}`, tokens_cache_read: sql`${SessionTable.tokens_cache_read} + ${value.tokens.cache.read * sign}`, tokens_cache_write: sql`${SessionTable.tokens_cache_write} + ${value.tokens.cache.write * sign}`, + time_updated: sql`${SessionTable.time_updated}`, }) .where(eq(SessionTable.id, sessionID)) .run() @@ -110,7 +111,7 @@ export default [ const info = data.info const row = db .update(SessionTable) - .set(toPartialRow(info as Session.Patch)) + .set({ time_updated: sql`${SessionTable.time_updated}`, ...toPartialRow(info as Session.Patch) }) .where(eq(SessionTable.id, data.sessionID)) .returning() .get() From ec4fdaf8e9275ac0f89d0c1734f4806cb6c7d37a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 12:42:19 -0400 Subject: [PATCH 104/378] test(tool): migrate tool define tests to Effect runner (#27097) --- .../opencode/test/tool/tool-define.test.ts | 119 +++++++++--------- 1 file changed, 62 insertions(+), 57 deletions(-) diff --git a/packages/opencode/test/tool/tool-define.test.ts b/packages/opencode/test/tool/tool-define.test.ts index a291b9f7f..ca351cca4 100644 --- a/packages/opencode/test/tool/tool-define.test.ts +++ b/packages/opencode/test/tool/tool-define.test.ts @@ -1,11 +1,12 @@ -import { describe, test, expect } from "bun:test" -import { Effect, Layer, ManagedRuntime, Schema } from "effect" +import { describe, expect } from "bun:test" +import { Effect, Layer, Schema } from "effect" import { Agent } from "../../src/agent/agent" import { MessageID, SessionID } from "../../src/session/schema" import { Tool } from "@/tool/tool" import { Truncate } from "@/tool/truncate" +import { testEffect } from "../lib/effect" -const runtime = ManagedRuntime.make(Layer.mergeAll(Truncate.defaultLayer, Agent.defaultLayer)) +const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, Agent.defaultLayer)) const params = Schema.Struct({ input: Schema.String }) @@ -21,49 +22,53 @@ function makeTool(id: string, executeFn?: () => void) { } describe("Tool.define", () => { - test("object-defined tool does not mutate the original init object", async () => { - const original = makeTool("test") - const originalExecute = original.execute + it.effect("object-defined tool does not mutate the original init object", () => + Effect.gen(function* () { + const original = makeTool("test") + const originalExecute = original.execute - const info = await runtime.runPromise(Tool.define("test-tool", Effect.succeed(original))) + const info = yield* Tool.define("test-tool", Effect.succeed(original)) - await Effect.runPromise(info.init()) - await Effect.runPromise(info.init()) - await Effect.runPromise(info.init()) + yield* info.init() + yield* info.init() + yield* info.init() - expect(original.execute).toBe(originalExecute) - }) + expect(original.execute).toBe(originalExecute) + }), + ) - test("effect-defined tool returns fresh objects and is unaffected", async () => { - const info = await runtime.runPromise( - Tool.define( + it.effect("effect-defined tool returns fresh objects and is unaffected", () => + Effect.gen(function* () { + const info = yield* Tool.define( "test-fn-tool", Effect.succeed(() => Effect.succeed(makeTool("test"))), - ), - ) + ) - const first = await Effect.runPromise(info.init()) - const second = await Effect.runPromise(info.init()) + const first = yield* info.init() + const second = yield* info.init() - expect(first).not.toBe(second) - }) + expect(first).not.toBe(second) + }), + ) - test("object-defined tool returns distinct objects per init() call", async () => { - const info = await runtime.runPromise(Tool.define("test-copy", Effect.succeed(makeTool("test")))) + it.effect("object-defined tool returns distinct objects per init() call", () => + Effect.gen(function* () { + const info = yield* Tool.define("test-copy", Effect.succeed(makeTool("test"))) - const first = await Effect.runPromise(info.init()) - const second = await Effect.runPromise(info.init()) + const first = yield* info.init() + const second = yield* info.init() - expect(first).not.toBe(second) - }) + expect(first).not.toBe(second) + }), + ) - test("execute receives decoded parameters", async () => { - const parameters = Schema.Struct({ - count: Schema.NumberFromString.pipe(Schema.optional, Schema.withDecodingDefaultType(Effect.succeed(5))), - }) - const calls: Array> = [] - const info = await runtime.runPromise( - Tool.define( + it.effect("execute receives decoded parameters", () => + Effect.gen(function* () { + const parameters = Schema.Struct({ + count: Schema.NumberFromString.pipe(Schema.optional, Schema.withDecodingDefaultType(Effect.succeed(5))), + }) + const calls: Array> = [] + const info = yield* Tool.define( "test-decoded", Effect.succeed({ description: "test tool", @@ -73,27 +78,27 @@ describe("Tool.define", () => { return Effect.succeed({ title: "test", output: "ok", metadata: { truncated: false } }) }, }), - ), - ) - const ctx: Tool.Context = { - sessionID: SessionID.descending(), - messageID: MessageID.ascending(), - agent: "build", - abort: new AbortController().signal, - messages: [], - metadata() { - return Effect.void - }, - ask() { - return Effect.void - }, - } - const tool = await Effect.runPromise(info.init()) - const execute = tool.execute as unknown as (args: unknown, ctx: Tool.Context) => ReturnType - - await Effect.runPromise(execute({}, ctx)) - await Effect.runPromise(execute({ count: "7" }, ctx)) - - expect(calls).toEqual([{ count: 5 }, { count: 7 }]) - }) + ) + const ctx: Tool.Context = { + sessionID: SessionID.descending(), + messageID: MessageID.ascending(), + agent: "build", + abort: new AbortController().signal, + messages: [], + metadata() { + return Effect.void + }, + ask() { + return Effect.void + }, + } + const tool = yield* info.init() + const execute = tool.execute as unknown as (args: unknown, ctx: Tool.Context) => ReturnType + + yield* execute({}, ctx) + yield* execute({ count: "7" }, ctx) + + expect(calls).toEqual([{ count: 5 }, { count: 7 }]) + }), + ) }) From 8115004c73559fc3ad0f9eda4066f8d47aa2e8cf Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 12:42:49 -0400 Subject: [PATCH 105/378] test(file): migrate path traversal tests to Effect runner (#27098) --- .../opencode/test/file/path-traversal.test.ts | 323 ++++++++---------- 1 file changed, 147 insertions(+), 176 deletions(-) diff --git a/packages/opencode/test/file/path-traversal.test.ts b/packages/opencode/test/file/path-traversal.test.ts index 5b59929ea..28bd34978 100644 --- a/packages/opencode/test/file/path-traversal.test.ts +++ b/packages/opencode/test/file/path-traversal.test.ts @@ -1,42 +1,55 @@ -import { test, expect, describe } from "bun:test" -import { Effect } from "effect" +import { expect, describe } from "bun:test" +import { Cause, Effect, Exit } from "effect" import path from "path" import fs from "fs/promises" import { Filesystem } from "@/util/filesystem" import { File } from "../../src/file" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { InstanceState } from "../../src/effect/instance-state" import { containsPath } from "../../src/project/instance-context" -import { provideInstance, tmpdir } from "../fixture/fixture" - -const run = (eff: Effect.Effect) => - Effect.runPromise(provideInstance(Instance.directory)(eff.pipe(Effect.provide(File.defaultLayer)))) -const read = (file: string) => run(File.Service.use((svc) => svc.read(file))) -const list = (dir?: string) => run(File.Service.use((svc) => svc.list(dir))) - -describe("Filesystem.contains", () => { - test("allows paths within project", () => { - expect(Filesystem.contains("/project", "/project/src")).toBe(true) - expect(Filesystem.contains("/project", "/project/src/file.ts")).toBe(true) - expect(Filesystem.contains("/project", "/project")).toBe(true) - }) - - test("blocks ../ traversal", () => { - expect(Filesystem.contains("/project", "/project/../etc")).toBe(false) - expect(Filesystem.contains("/project", "/project/src/../../etc")).toBe(false) - expect(Filesystem.contains("/project", "/etc/passwd")).toBe(false) - }) - - test("blocks absolute paths outside project", () => { - expect(Filesystem.contains("/project", "/etc/passwd")).toBe(false) - expect(Filesystem.contains("/project", "/tmp/file")).toBe(false) - expect(Filesystem.contains("/home/user/project", "/home/user/other")).toBe(false) +import { TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" + +const it = testEffect(File.defaultLayer) +const read = (file: string) => File.Service.use((svc) => svc.read(file)) +const list = (dir?: string) => File.Service.use((svc) => svc.list(dir)) +const expectAccessDenied = (effect: Effect.Effect) => + Effect.gen(function* () { + const exit = yield* effect.pipe(Effect.exit) + if (Exit.isSuccess(exit)) throw new Error("expected access denied") + expect(Cause.squash(exit.cause)).toHaveProperty("message", "Access denied: path escapes project directory") }) - test("handles prefix collision edge cases", () => { - expect(Filesystem.contains("/project", "/project-other/file")).toBe(false) - expect(Filesystem.contains("/project", "/projectfile")).toBe(false) - }) +describe("Filesystem.contains", () => { + it.effect("allows paths within project", () => + Effect.sync(() => { + expect(Filesystem.contains("/project", "/project/src")).toBe(true) + expect(Filesystem.contains("/project", "/project/src/file.ts")).toBe(true) + expect(Filesystem.contains("/project", "/project")).toBe(true) + }), + ) + + it.effect("blocks ../ traversal", () => + Effect.sync(() => { + expect(Filesystem.contains("/project", "/project/../etc")).toBe(false) + expect(Filesystem.contains("/project", "/project/src/../../etc")).toBe(false) + expect(Filesystem.contains("/project", "/etc/passwd")).toBe(false) + }), + ) + + it.effect("blocks absolute paths outside project", () => + Effect.sync(() => { + expect(Filesystem.contains("/project", "/etc/passwd")).toBe(false) + expect(Filesystem.contains("/project", "/tmp/file")).toBe(false) + expect(Filesystem.contains("/home/user/project", "/home/user/other")).toBe(false) + }), + ) + + it.effect("handles prefix collision edge cases", () => + Effect.sync(() => { + expect(Filesystem.contains("/project", "/project-other/file")).toBe(false) + expect(Filesystem.contains("/project", "/projectfile")).toBe(false) + }), + ) }) /* @@ -49,158 +62,116 @@ describe("Filesystem.contains", () => { * This is a SEPARATE code path from ReadTool, which has its own checks. */ describe("File.read path traversal protection", () => { - test("rejects ../ traversal attempting to read /etc/passwd", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "allowed.txt"), "allowed content") - }, - }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(read("../../../etc/passwd")).rejects.toThrow("Access denied: path escapes project directory") - }, - }) - }) - - test("rejects deeply nested traversal", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(read("src/nested/../../../../../../../etc/passwd")).rejects.toThrow( - "Access denied: path escapes project directory", - ) - }, - }) - }) - - test("allows valid paths within project", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "valid.txt"), "valid content") - }, - }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await read("valid.txt") - expect(result.content).toBe("valid content") - }, - }) - }) + it.instance("rejects ../ traversal attempting to read /etc/passwd", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => Bun.write(path.join(test.directory, "allowed.txt"), "allowed content")) + yield* expectAccessDenied(read("../../../etc/passwd")) + }), + ) + + it.instance("rejects deeply nested traversal", () => + Effect.gen(function* () { + yield* expectAccessDenied(read("src/nested/../../../../../../../etc/passwd")) + }), + ) + + it.instance("allows valid paths within project", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => Bun.write(path.join(test.directory, "valid.txt"), "valid content")) + + const result = yield* read("valid.txt") + expect(result.content).toBe("valid content") + }), + ) }) describe("File.list path traversal protection", () => { - test("rejects ../ traversal attempting to list /etc", async () => { - await using tmp = await tmpdir() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(list("../../../etc")).rejects.toThrow("Access denied: path escapes project directory") - }, - }) - }) - - test("allows valid subdirectory listing", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "subdir", "file.txt"), "content") - }, - }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const result = await list("subdir") - expect(Array.isArray(result)).toBe(true) - }, - }) - }) + it.instance("rejects ../ traversal attempting to list /etc", () => + Effect.gen(function* () { + yield* expectAccessDenied(list("../../../etc")) + }), + ) + + it.instance("allows valid subdirectory listing", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* Effect.promise(() => Bun.write(path.join(test.directory, "subdir", "file.txt"), "content")) + + const result = yield* list("subdir") + expect(Array.isArray(result)).toBe(true) + }), + ) }) describe("containsPath", () => { - test("returns true for path inside directory", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: () => { - expect(containsPath(path.join(tmp.path, "foo.txt"), Instance.current)).toBe(true) - expect(containsPath(path.join(tmp.path, "src", "file.ts"), Instance.current)).toBe(true) - }, - }) - }) - - test("returns true for path inside worktree but outside directory (monorepo subdirectory scenario)", async () => { - await using tmp = await tmpdir({ git: true }) - const subdir = path.join(tmp.path, "packages", "lib") - await fs.mkdir(subdir, { recursive: true }) + it.instance("returns true for path inside directory", () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceState.context + expect(containsPath(path.join(test.directory, "foo.txt"), ctx)).toBe(true) + expect(containsPath(path.join(test.directory, "src", "file.ts"), ctx)).toBe(true) + }), + { git: true }, + ) + + it.instance( + "returns true for path inside worktree but outside directory (monorepo subdirectory scenario)", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const subdir = path.join(test.directory, "packages", "lib") + yield* Effect.promise(() => fs.mkdir(subdir, { recursive: true })) + const ctx = { ...(yield* InstanceState.context), directory: subdir } - await WithInstance.provide({ - directory: subdir, - fn: () => { // .opencode at worktree root, but we're running from packages/lib - expect(containsPath(path.join(tmp.path, ".opencode", "state"), Instance.current)).toBe(true) + expect(containsPath(path.join(test.directory, ".opencode", "state"), ctx)).toBe(true) // sibling package should also be accessible - expect(containsPath(path.join(tmp.path, "packages", "other", "file.ts"), Instance.current)).toBe(true) + expect(containsPath(path.join(test.directory, "packages", "other", "file.ts"), ctx)).toBe(true) // worktree root itself - expect(containsPath(tmp.path, Instance.current)).toBe(true) - }, - }) - }) - - test("returns false for path outside both directory and worktree", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: () => { - expect(containsPath("/etc/passwd", Instance.current)).toBe(false) - expect(containsPath("/tmp/other-project", Instance.current)).toBe(false) - }, - }) - }) - - test("returns false for path with .. escaping worktree", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: () => { - expect(containsPath(path.join(tmp.path, "..", "escape.txt"), Instance.current)).toBe(false) - }, - }) - }) - - test("handles directory === worktree (running from repo root)", async () => { - await using tmp = await tmpdir({ git: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: () => { - expect(Instance.directory).toBe(Instance.worktree) - expect(containsPath(path.join(tmp.path, "file.txt"), Instance.current)).toBe(true) - expect(containsPath("/etc/passwd", Instance.current)).toBe(false) - }, - }) - }) - - test("non-git project does not allow arbitrary paths via worktree='/'", async () => { - await using tmp = await tmpdir() // no git: true - - await WithInstance.provide({ - directory: tmp.path, - fn: () => { - // worktree is "/" for non-git projects, but containsPath should NOT allow all paths - expect(containsPath(path.join(tmp.path, "file.txt"), Instance.current)).toBe(true) - expect(containsPath("/etc/passwd", Instance.current)).toBe(false) - expect(containsPath("/tmp/other", Instance.current)).toBe(false) - }, - }) - }) + expect(containsPath(test.directory, ctx)).toBe(true) + }), + { git: true }, + ) + + it.instance("returns false for path outside both directory and worktree", () => + Effect.gen(function* () { + const ctx = yield* InstanceState.context + expect(containsPath("/etc/passwd", ctx)).toBe(false) + expect(containsPath("/tmp/other-project", ctx)).toBe(false) + }), + { git: true }, + ) + + it.instance("returns false for path with .. escaping worktree", () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceState.context + expect(containsPath(path.join(test.directory, "..", "escape.txt"), ctx)).toBe(false) + }), + { git: true }, + ) + + it.instance("handles directory === worktree (running from repo root)", () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceState.context + expect(ctx.directory).toBe(ctx.worktree) + expect(containsPath(path.join(test.directory, "file.txt"), ctx)).toBe(true) + expect(containsPath("/etc/passwd", ctx)).toBe(false) + }), + { git: true }, + ) + + it.instance("non-git project does not allow arbitrary paths via worktree='/'", () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceState.context + // worktree is "/" for non-git projects, but containsPath should NOT allow all paths + expect(containsPath(path.join(test.directory, "file.txt"), ctx)).toBe(true) + expect(containsPath("/etc/passwd", ctx)).toBe(false) + expect(containsPath("/tmp/other", ctx)).toBe(false) + }), + ) }) From a16789dfdd47e208054f0d5fdb1d4a3be53b9ada Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 12:43:33 -0400 Subject: [PATCH 106/378] test(tool): migrate apply patch tests to Effect runner (#27100) --- .../opencode/test/tool/apply_patch.test.ts | 891 ++++++++---------- 1 file changed, 402 insertions(+), 489 deletions(-) diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index 3fc034e4e..190254866 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -1,20 +1,19 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect } from "bun:test" import path from "path" import * as fs from "fs/promises" -import { Effect, ManagedRuntime, Layer } from "effect" +import { Cause, Effect, Exit, Layer } from "effect" import { ApplyPatchTool } from "../../src/tool/apply_patch" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { LSP } from "@/lsp/lsp" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Format } from "../../src/format" import { Agent } from "../../src/agent/agent" import { Bus } from "../../src/bus" import { Truncate } from "@/tool/truncate" -import { tmpdir } from "../fixture/fixture" +import { TestInstance } from "../fixture/fixture" import { SessionID, MessageID } from "../../src/session/schema" +import { testEffect } from "../lib/effect" -const runtime = ManagedRuntime.make( +const it = testEffect( Layer.mergeAll( LSP.defaultLayer, AppFileSystem.defaultLayer, @@ -58,11 +57,11 @@ type ToolCtx = typeof baseCtx & { ask: (input: AskInput) => Effect.Effect } -const execute = async (params: { patchText: string }, ctx: ToolCtx) => { - const info = await runtime.runPromise(ApplyPatchTool) - const tool = await runtime.runPromise(info.init()) - return Effect.runPromise(tool.execute(params, ctx)) -} +const execute = Effect.fn("ApplyPatchToolTest.execute")(function* (params: { patchText: string }, ctx: ToolCtx) { + const info = yield* ApplyPatchTool + const tool = yield* info.init() + return yield* tool.execute(params, ctx) +}) const makeCtx = () => { const calls: AskInput[] = [] @@ -77,39 +76,56 @@ const makeCtx = () => { return { ctx, calls } } -describe("tool.apply_patch freeform", () => { - test("requires patchText", async () => { - const { ctx } = makeCtx() - await expect(execute({ patchText: "" }, ctx)).rejects.toThrow("patchText is required") - }) - - test("rejects invalid patch format", async () => { - const { ctx } = makeCtx() - await expect(execute({ patchText: "invalid patch" }, ctx)).rejects.toThrow("apply_patch verification failed") - }) +const readText = (filepath: string) => Effect.promise(() => fs.readFile(filepath, "utf-8")) +const writeText = (filepath: string, content: string) => Effect.promise(() => fs.writeFile(filepath, content, "utf-8")) +const makeDir = (dir: string) => Effect.promise(() => fs.mkdir(dir, { recursive: true })) - test("rejects empty patch", async () => { - const { ctx } = makeCtx() - const emptyPatch = "*** Begin Patch\n*** End Patch" - await expect(execute({ patchText: emptyPatch }, ctx)).rejects.toThrow("patch rejected: empty patch") +const expectFailure = (effect: Effect.Effect, message?: string) => + Effect.gen(function* () { + const exit = yield* Effect.exit(effect) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit) && message) expect(Cause.pretty(exit.cause)).toContain(message) }) - test("applies add/update/delete in one patch", async () => { - await using fixture = await tmpdir({ git: true }) - const { ctx, calls } = makeCtx() +const expectReadFailure = (filepath: string) => expectFailure(readText(filepath)) - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const modifyPath = path.join(fixture.path, "modify.txt") - const deletePath = path.join(fixture.path, "delete.txt") - await fs.writeFile(modifyPath, "line1\nline2\n", "utf-8") - await fs.writeFile(deletePath, "obsolete\n", "utf-8") +describe("tool.apply_patch freeform", () => { + it.live("requires patchText", () => + Effect.gen(function* () { + const { ctx } = makeCtx() + yield* expectFailure(execute({ patchText: "" }, ctx), "patchText is required") + }), + ) + + it.live("rejects invalid patch format", () => + Effect.gen(function* () { + const { ctx } = makeCtx() + yield* expectFailure(execute({ patchText: "invalid patch" }, ctx), "apply_patch verification failed") + }), + ) + + it.live("rejects empty patch", () => + Effect.gen(function* () { + const { ctx } = makeCtx() + yield* expectFailure(execute({ patchText: "*** Begin Patch\n*** End Patch" }, ctx), "patch rejected: empty patch") + }), + ) + + it.instance( + "applies add/update/delete in one patch", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx, calls } = makeCtx() + const modifyPath = path.join(test.directory, "modify.txt") + const deletePath = path.join(test.directory, "delete.txt") + yield* writeText(modifyPath, "line1\nline2\n") + yield* writeText(deletePath, "obsolete\n") const patchText = "*** Begin Patch\n*** Add File: nested/new.txt\n+created\n*** Delete File: delete.txt\n*** Update File: modify.txt\n@@\n-line2\n+changed\n*** End Patch" - const result = await execute({ patchText }, ctx) + const result = yield* execute({ patchText }, ctx) expect(result.title).toContain("Success. Updated the following files") expect(result.output).toContain("Success. Updated the following files") @@ -129,38 +145,34 @@ describe("tool.apply_patch freeform", () => { expect(permissionCall.metadata.files.map((f) => f.type).sort()).toEqual(["add", "delete", "update"]) const addFile = permissionCall.metadata.files.find((f) => f.type === "add") - expect(addFile).toBeDefined() - expect(addFile!.relativePath).toBe("nested/new.txt") - expect(addFile!.patch).toContain("+created") + expect(addFile?.relativePath).toBe("nested/new.txt") + expect(addFile?.patch).toContain("+created") const updateFile = permissionCall.metadata.files.find((f) => f.type === "update") - expect(updateFile).toBeDefined() - expect(updateFile!.patch).toContain("-line2") - expect(updateFile!.patch).toContain("+changed") - - const added = await fs.readFile(path.join(fixture.path, "nested", "new.txt"), "utf-8") - expect(added).toBe("created\n") - expect(await fs.readFile(modifyPath, "utf-8")).toBe("line1\nchanged\n") - await expect(fs.readFile(deletePath, "utf-8")).rejects.toThrow() - }, - }) - }) + expect(updateFile?.patch).toContain("-line2") + expect(updateFile?.patch).toContain("+changed") - test("permission metadata includes move file info", async () => { - await using fixture = await tmpdir({ git: true }) - const { ctx, calls } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const original = path.join(fixture.path, "old", "name.txt") - await fs.mkdir(path.dirname(original), { recursive: true }) - await fs.writeFile(original, "old content\n", "utf-8") + expect(yield* readText(path.join(test.directory, "nested", "new.txt"))).toBe("created\n") + expect(yield* readText(modifyPath)).toBe("line1\nchanged\n") + yield* expectReadFailure(deletePath) + }), + { git: true }, + ) + + it.instance( + "permission metadata includes move file info", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx, calls } = makeCtx() + const original = path.join(test.directory, "old", "name.txt") + yield* makeDir(path.dirname(original)) + yield* writeText(original, "old content\n") const patchText = "*** Begin Patch\n*** Update File: old/name.txt\n*** Move to: renamed/dir/name.txt\n@@\n-old content\n+new content\n*** End Patch" - await execute({ patchText }, ctx) + yield* execute({ patchText }, ctx) expect(calls.length).toBe(1) const permissionCall = calls[0] @@ -169,447 +181,348 @@ describe("tool.apply_patch freeform", () => { const moveFile = permissionCall.metadata.files[0] expect(moveFile.type).toBe("move") expect(moveFile.relativePath).toBe("renamed/dir/name.txt") - expect(moveFile.movePath).toBe(path.join(fixture.path, "renamed/dir/name.txt")) + expect(moveFile.movePath).toBe(path.join(test.directory, "renamed/dir/name.txt")) expect(moveFile.patch).toContain("-old content") expect(moveFile.patch).toContain("+new content") - }, - }) - }) - - test("applies multiple hunks to one file", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "multi.txt") - await fs.writeFile(target, "line1\nline2\nline3\nline4\n", "utf-8") - - const patchText = - "*** Begin Patch\n*** Update File: multi.txt\n@@\n-line2\n+changed2\n@@\n-line4\n+changed4\n*** End Patch" - - await execute({ patchText }, ctx) - - expect(await fs.readFile(target, "utf-8")).toBe("line1\nchanged2\nline3\nchanged4\n") - }, - }) - }) - - test("does not invent a first-line diff for BOM files", async () => { - await using fixture = await tmpdir() - const { ctx, calls } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const bom = String.fromCharCode(0xfeff) - const target = path.join(fixture.path, "example.cs") - await fs.writeFile(target, `${bom}using System;\n\nclass Test {}\n`, "utf-8") - - const patchText = - "*** Begin Patch\n*** Update File: example.cs\n@@\n class Test {}\n+class Next {}\n*** End Patch" - - await execute({ patchText }, ctx) - - expect(calls.length).toBe(1) - const shown = calls[0].metadata.files[0]?.patch ?? "" - expect(shown).not.toContain(bom) - expect(shown).not.toContain("-using System;") - expect(shown).not.toContain("+using System;") - - const content = await fs.readFile(target, "utf-8") - expect(content.charCodeAt(0)).toBe(0xfeff) - expect(content.slice(1)).toBe("using System;\n\nclass Test {}\nclass Next {}\n") - }, - }) - }) - - test("inserts lines with insert-only hunk", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "insert_only.txt") - await fs.writeFile(target, "alpha\nomega\n", "utf-8") - - const patchText = "*** Begin Patch\n*** Update File: insert_only.txt\n@@\n alpha\n+beta\n omega\n*** End Patch" - - await execute({ patchText }, ctx) - - expect(await fs.readFile(target, "utf-8")).toBe("alpha\nbeta\nomega\n") - }, - }) - }) - - test("appends trailing newline on update", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "no_newline.txt") - await fs.writeFile(target, "no newline at end", "utf-8") - - const patchText = - "*** Begin Patch\n*** Update File: no_newline.txt\n@@\n-no newline at end\n+first line\n+second line\n*** End Patch" - - await execute({ patchText }, ctx) - - const contents = await fs.readFile(target, "utf-8") - expect(contents.endsWith("\n")).toBe(true) - expect(contents).toBe("first line\nsecond line\n") - }, - }) - }) - - test("moves file to a new directory", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const original = path.join(fixture.path, "old", "name.txt") - await fs.mkdir(path.dirname(original), { recursive: true }) - await fs.writeFile(original, "old content\n", "utf-8") - - const patchText = - "*** Begin Patch\n*** Update File: old/name.txt\n*** Move to: renamed/dir/name.txt\n@@\n-old content\n+new content\n*** End Patch" - - await execute({ patchText }, ctx) - - const moved = path.join(fixture.path, "renamed", "dir", "name.txt") - await expect(fs.readFile(original, "utf-8")).rejects.toThrow() - expect(await fs.readFile(moved, "utf-8")).toBe("new content\n") - }, - }) - }) - - test("moves file overwriting existing destination", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const original = path.join(fixture.path, "old", "name.txt") - const destination = path.join(fixture.path, "renamed", "dir", "name.txt") - await fs.mkdir(path.dirname(original), { recursive: true }) - await fs.mkdir(path.dirname(destination), { recursive: true }) - await fs.writeFile(original, "from\n", "utf-8") - await fs.writeFile(destination, "existing\n", "utf-8") - - const patchText = - "*** Begin Patch\n*** Update File: old/name.txt\n*** Move to: renamed/dir/name.txt\n@@\n-from\n+new\n*** End Patch" - - await execute({ patchText }, ctx) - - await expect(fs.readFile(original, "utf-8")).rejects.toThrow() - expect(await fs.readFile(destination, "utf-8")).toBe("new\n") - }, - }) - }) - - test("adds file overwriting existing file", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "duplicate.txt") - await fs.writeFile(target, "old content\n", "utf-8") - - const patchText = "*** Begin Patch\n*** Add File: duplicate.txt\n+new content\n*** End Patch" - - await execute({ patchText }, ctx) - expect(await fs.readFile(target, "utf-8")).toBe("new content\n") - }, - }) - }) - - test("rejects update when target file is missing", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const patchText = "*** Begin Patch\n*** Update File: missing.txt\n@@\n-nope\n+better\n*** End Patch" - - await expect(execute({ patchText }, ctx)).rejects.toThrow( - "apply_patch verification failed: Failed to read file to update", - ) - }, - }) - }) - - test("rejects delete when file is missing", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const patchText = "*** Begin Patch\n*** Delete File: missing.txt\n*** End Patch" - - await expect(execute({ patchText }, ctx)).rejects.toThrow() - }, - }) - }) - - test("rejects delete when target is a directory", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const dirPath = path.join(fixture.path, "dir") - await fs.mkdir(dirPath) - - const patchText = "*** Begin Patch\n*** Delete File: dir\n*** End Patch" - - await expect(execute({ patchText }, ctx)).rejects.toThrow() - }, - }) - }) - - test("rejects invalid hunk header", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const patchText = "*** Begin Patch\n*** Frobnicate File: foo\n*** End Patch" - - await expect(execute({ patchText }, ctx)).rejects.toThrow("apply_patch verification failed") - }, - }) - }) - - test("rejects update with missing context", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "modify.txt") - await fs.writeFile(target, "line1\nline2\n", "utf-8") - - const patchText = "*** Begin Patch\n*** Update File: modify.txt\n@@\n-missing\n+changed\n*** End Patch" - - await expect(execute({ patchText }, ctx)).rejects.toThrow("apply_patch verification failed") - expect(await fs.readFile(target, "utf-8")).toBe("line1\nline2\n") - }, - }) - }) - - test("verification failure leaves no side effects", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const patchText = - "*** Begin Patch\n*** Add File: created.txt\n+hello\n*** Update File: missing.txt\n@@\n-old\n+new\n*** End Patch" - - await expect(execute({ patchText }, ctx)).rejects.toThrow() - - const createdPath = path.join(fixture.path, "created.txt") - await expect(fs.readFile(createdPath, "utf-8")).rejects.toThrow() - }, - }) - }) - - test("supports end of file anchor", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "tail.txt") - await fs.writeFile(target, "alpha\nlast\n", "utf-8") - - const patchText = "*** Begin Patch\n*** Update File: tail.txt\n@@\n-last\n+end\n*** End of File\n*** End Patch" - - await execute({ patchText }, ctx) - expect(await fs.readFile(target, "utf-8")).toBe("alpha\nend\n") - }, - }) - }) - - test("rejects missing second chunk context", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "two_chunks.txt") - await fs.writeFile(target, "a\nb\nc\nd\n", "utf-8") - - const patchText = "*** Begin Patch\n*** Update File: two_chunks.txt\n@@\n-b\n+B\n\n-d\n+D\n*** End Patch" - - await expect(execute({ patchText }, ctx)).rejects.toThrow() - expect(await fs.readFile(target, "utf-8")).toBe("a\nb\nc\nd\n") - }, - }) - }) - - test("disambiguates change context with @@ header", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "multi_ctx.txt") - await fs.writeFile(target, "fn a\nx=10\ny=2\nfn b\nx=10\ny=20\n", "utf-8") - - const patchText = "*** Begin Patch\n*** Update File: multi_ctx.txt\n@@ fn b\n-x=10\n+x=11\n*** End Patch" - - await execute({ patchText }, ctx) - expect(await fs.readFile(target, "utf-8")).toBe("fn a\nx=10\ny=2\nfn b\nx=11\ny=20\n") - }, - }) - }) - - test("EOF anchor matches from end of file first", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "eof_anchor.txt") - // File has duplicate "marker" lines - one in middle, one at end - await fs.writeFile(target, "start\nmarker\nmiddle\nmarker\nend\n", "utf-8") - - // With EOF anchor, should match the LAST "marker" line, not the first - const patchText = - "*** Begin Patch\n*** Update File: eof_anchor.txt\n@@\n-marker\n-end\n+marker-changed\n+end\n*** End of File\n*** End Patch" - - await execute({ patchText }, ctx) - // First marker unchanged, second marker changed - expect(await fs.readFile(target, "utf-8")).toBe("start\nmarker\nmiddle\nmarker-changed\nend\n") - }, - }) - }) - - test("parses heredoc-wrapped patch", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const patchText = `cat <<'EOF' + }), + { git: true }, + ) + + it.instance("applies multiple hunks to one file", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "multi.txt") + yield* writeText(target, "line1\nline2\nline3\nline4\n") + + const patchText = + "*** Begin Patch\n*** Update File: multi.txt\n@@\n-line2\n+changed2\n@@\n-line4\n+changed4\n*** End Patch" + + yield* execute({ patchText }, ctx) + + expect(yield* readText(target)).toBe("line1\nchanged2\nline3\nchanged4\n") + }), + ) + + it.instance("does not invent a first-line diff for BOM files", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx, calls } = makeCtx() + const bom = String.fromCharCode(0xfeff) + const target = path.join(test.directory, "example.cs") + yield* writeText(target, `${bom}using System;\n\nclass Test {}\n`) + + const patchText = "*** Begin Patch\n*** Update File: example.cs\n@@\n class Test {}\n+class Next {}\n*** End Patch" + + yield* execute({ patchText }, ctx) + + expect(calls.length).toBe(1) + const shown = calls[0].metadata.files[0]?.patch ?? "" + expect(shown).not.toContain(bom) + expect(shown).not.toContain("-using System;") + expect(shown).not.toContain("+using System;") + + const content = yield* readText(target) + expect(content.charCodeAt(0)).toBe(0xfeff) + expect(content.slice(1)).toBe("using System;\n\nclass Test {}\nclass Next {}\n") + }), + ) + + it.instance("inserts lines with insert-only hunk", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "insert_only.txt") + yield* writeText(target, "alpha\nomega\n") + + const patchText = "*** Begin Patch\n*** Update File: insert_only.txt\n@@\n alpha\n+beta\n omega\n*** End Patch" + + yield* execute({ patchText }, ctx) + + expect(yield* readText(target)).toBe("alpha\nbeta\nomega\n") + }), + ) + + it.instance("appends trailing newline on update", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "no_newline.txt") + yield* writeText(target, "no newline at end") + + const patchText = + "*** Begin Patch\n*** Update File: no_newline.txt\n@@\n-no newline at end\n+first line\n+second line\n*** End Patch" + + yield* execute({ patchText }, ctx) + + const contents = yield* readText(target) + expect(contents.endsWith("\n")).toBe(true) + expect(contents).toBe("first line\nsecond line\n") + }), + ) + + it.instance("moves file to a new directory", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const original = path.join(test.directory, "old", "name.txt") + yield* makeDir(path.dirname(original)) + yield* writeText(original, "old content\n") + + const patchText = + "*** Begin Patch\n*** Update File: old/name.txt\n*** Move to: renamed/dir/name.txt\n@@\n-old content\n+new content\n*** End Patch" + + yield* execute({ patchText }, ctx) + + const moved = path.join(test.directory, "renamed", "dir", "name.txt") + yield* expectReadFailure(original) + expect(yield* readText(moved)).toBe("new content\n") + }), + ) + + it.instance("moves file overwriting existing destination", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const original = path.join(test.directory, "old", "name.txt") + const destination = path.join(test.directory, "renamed", "dir", "name.txt") + yield* makeDir(path.dirname(original)) + yield* makeDir(path.dirname(destination)) + yield* writeText(original, "from\n") + yield* writeText(destination, "existing\n") + + const patchText = + "*** Begin Patch\n*** Update File: old/name.txt\n*** Move to: renamed/dir/name.txt\n@@\n-from\n+new\n*** End Patch" + + yield* execute({ patchText }, ctx) + + yield* expectReadFailure(original) + expect(yield* readText(destination)).toBe("new\n") + }), + ) + + it.instance("adds file overwriting existing file", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "duplicate.txt") + yield* writeText(target, "old content\n") + + const patchText = "*** Begin Patch\n*** Add File: duplicate.txt\n+new content\n*** End Patch" + + yield* execute({ patchText }, ctx) + expect(yield* readText(target)).toBe("new content\n") + }), + ) + + it.instance("rejects update when target file is missing", () => + Effect.gen(function* () { + const { ctx } = makeCtx() + const patchText = "*** Begin Patch\n*** Update File: missing.txt\n@@\n-nope\n+better\n*** End Patch" + + yield* expectFailure(execute({ patchText }, ctx), "apply_patch verification failed: Failed to read file to update") + }), + ) + + it.instance("rejects delete when file is missing", () => + Effect.gen(function* () { + const { ctx } = makeCtx() + const patchText = "*** Begin Patch\n*** Delete File: missing.txt\n*** End Patch" + + yield* expectFailure(execute({ patchText }, ctx)) + }), + ) + + it.instance("rejects delete when target is a directory", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const dirPath = path.join(test.directory, "dir") + yield* makeDir(dirPath) + + const patchText = "*** Begin Patch\n*** Delete File: dir\n*** End Patch" + + yield* expectFailure(execute({ patchText }, ctx)) + }), + ) + + it.instance("rejects invalid hunk header", () => + Effect.gen(function* () { + const { ctx } = makeCtx() + const patchText = "*** Begin Patch\n*** Frobnicate File: foo\n*** End Patch" + + yield* expectFailure(execute({ patchText }, ctx), "apply_patch verification failed") + }), + ) + + it.instance("rejects update with missing context", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "modify.txt") + yield* writeText(target, "line1\nline2\n") + + const patchText = "*** Begin Patch\n*** Update File: modify.txt\n@@\n-missing\n+changed\n*** End Patch" + + yield* expectFailure(execute({ patchText }, ctx), "apply_patch verification failed") + expect(yield* readText(target)).toBe("line1\nline2\n") + }), + ) + + it.instance("verification failure leaves no side effects", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const patchText = + "*** Begin Patch\n*** Add File: created.txt\n+hello\n*** Update File: missing.txt\n@@\n-old\n+new\n*** End Patch" + + yield* expectFailure(execute({ patchText }, ctx)) + yield* expectReadFailure(path.join(test.directory, "created.txt")) + }), + ) + + it.instance("supports end of file anchor", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "tail.txt") + yield* writeText(target, "alpha\nlast\n") + + const patchText = "*** Begin Patch\n*** Update File: tail.txt\n@@\n-last\n+end\n*** End of File\n*** End Patch" + + yield* execute({ patchText }, ctx) + expect(yield* readText(target)).toBe("alpha\nend\n") + }), + ) + + it.instance("rejects missing second chunk context", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "two_chunks.txt") + yield* writeText(target, "a\nb\nc\nd\n") + + const patchText = "*** Begin Patch\n*** Update File: two_chunks.txt\n@@\n-b\n+B\n\n-d\n+D\n*** End Patch" + + yield* expectFailure(execute({ patchText }, ctx)) + expect(yield* readText(target)).toBe("a\nb\nc\nd\n") + }), + ) + + it.instance("disambiguates change context with @@ header", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "multi_ctx.txt") + yield* writeText(target, "fn a\nx=10\ny=2\nfn b\nx=10\ny=20\n") + + const patchText = "*** Begin Patch\n*** Update File: multi_ctx.txt\n@@ fn b\n-x=10\n+x=11\n*** End Patch" + + yield* execute({ patchText }, ctx) + expect(yield* readText(target)).toBe("fn a\nx=10\ny=2\nfn b\nx=11\ny=20\n") + }), + ) + + it.instance("EOF anchor matches from end of file first", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "eof_anchor.txt") + // File has duplicate "marker" lines - one in middle, one at end + yield* writeText(target, "start\nmarker\nmiddle\nmarker\nend\n") + + // With EOF anchor, should match the LAST "marker" line, not the first + const patchText = + "*** Begin Patch\n*** Update File: eof_anchor.txt\n@@\n-marker\n-end\n+marker-changed\n+end\n*** End of File\n*** End Patch" + + yield* execute({ patchText }, ctx) + // First marker unchanged, second marker changed + expect(yield* readText(target)).toBe("start\nmarker\nmiddle\nmarker-changed\nend\n") + }), + ) + + it.instance("parses heredoc-wrapped patch", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const patchText = `cat <<'EOF' *** Begin Patch *** Add File: heredoc_test.txt +heredoc content *** End Patch EOF` - await execute({ patchText }, ctx) - const content = await fs.readFile(path.join(fixture.path, "heredoc_test.txt"), "utf-8") - expect(content).toBe("heredoc content\n") - }, - }) - }) - - test("parses heredoc-wrapped patch without cat", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() + yield* execute({ patchText }, ctx) + expect(yield* readText(path.join(test.directory, "heredoc_test.txt"))).toBe("heredoc content\n") + }), + ) - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const patchText = `< + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const patchText = `< { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "trailing_ws.txt") - // File has trailing spaces on some lines - await fs.writeFile(target, "line1 \nline2\nline3 \n", "utf-8") - - // Patch doesn't have trailing spaces - should still match via rstrip pass - const patchText = "*** Begin Patch\n*** Update File: trailing_ws.txt\n@@\n-line2\n+changed\n*** End Patch" - - await execute({ patchText }, ctx) - expect(await fs.readFile(target, "utf-8")).toBe("line1 \nchanged\nline3 \n") - }, - }) - }) - - test("matches with leading whitespace differences", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "leading_ws.txt") - // File has leading spaces - await fs.writeFile(target, " line1\nline2\n line3\n", "utf-8") - - // Patch without leading spaces - should match via trim pass - const patchText = "*** Begin Patch\n*** Update File: leading_ws.txt\n@@\n-line2\n+changed\n*** End Patch" - - await execute({ patchText }, ctx) - expect(await fs.readFile(target, "utf-8")).toBe(" line1\nchanged\n line3\n") - }, - }) - }) - - test("matches with Unicode punctuation differences", async () => { - await using fixture = await tmpdir() - const { ctx } = makeCtx() - - await WithInstance.provide({ - directory: fixture.path, - fn: async () => { - const target = path.join(fixture.path, "unicode.txt") - // File has fancy Unicode quotes (U+201C, U+201D) and em-dash (U+2014) - const leftQuote = "\u201C" - const rightQuote = "\u201D" - const emDash = "\u2014" - await fs.writeFile(target, `He said ${leftQuote}hello${rightQuote}\nsome${emDash}dash\nend\n`, "utf-8") - - // Patch uses ASCII equivalents - should match via normalized pass - // The replacement uses ASCII quotes from the patch (not preserving Unicode) - const patchText = - '*** Begin Patch\n*** Update File: unicode.txt\n@@\n-He said "hello"\n+He said "hi"\n*** End Patch' - - await execute({ patchText }, ctx) - // Result has ASCII quotes because that's what the patch specifies - expect(await fs.readFile(target, "utf-8")).toBe(`He said "hi"\nsome${emDash}dash\nend\n`) - }, - }) - }) + yield* execute({ patchText }, ctx) + expect(yield* readText(path.join(test.directory, "heredoc_no_cat.txt"))).toBe("no cat prefix\n") + }), + ) + + it.instance("matches with trailing whitespace differences", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "trailing_ws.txt") + // File has trailing spaces on some lines + yield* writeText(target, "line1 \nline2\nline3 \n") + + // Patch doesn't have trailing spaces - should still match via rstrip pass + const patchText = "*** Begin Patch\n*** Update File: trailing_ws.txt\n@@\n-line2\n+changed\n*** End Patch" + + yield* execute({ patchText }, ctx) + expect(yield* readText(target)).toBe("line1 \nchanged\nline3 \n") + }), + ) + + it.instance("matches with leading whitespace differences", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "leading_ws.txt") + // File has leading spaces + yield* writeText(target, " line1\nline2\n line3\n") + + // Patch without leading spaces - should match via trim pass + const patchText = "*** Begin Patch\n*** Update File: leading_ws.txt\n@@\n-line2\n+changed\n*** End Patch" + + yield* execute({ patchText }, ctx) + expect(yield* readText(target)).toBe(" line1\nchanged\n line3\n") + }), + ) + + it.instance("matches with Unicode punctuation differences", () => + Effect.gen(function* () { + const test = yield* TestInstance + const { ctx } = makeCtx() + const target = path.join(test.directory, "unicode.txt") + // File has fancy Unicode quotes (U+201C, U+201D) and em-dash (U+2014) + const leftQuote = "\u201C" + const rightQuote = "\u201D" + const emDash = "\u2014" + yield* writeText(target, `He said ${leftQuote}hello${rightQuote}\nsome${emDash}dash\nend\n`) + + // Patch uses ASCII equivalents - should match via normalized pass + // The replacement uses ASCII quotes from the patch (not preserving Unicode) + const patchText = '*** Begin Patch\n*** Update File: unicode.txt\n@@\n-He said "hello"\n+He said "hi"\n*** End Patch' + + yield* execute({ patchText }, ctx) + // Result has ASCII quotes because that's what the patch specifies + expect(yield* readText(target)).toBe(`He said "hi"\nsome${emDash}dash\nend\n`) + }), + ) }) From a7b50416745122c3f7134a32813a937e1daab78a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 12:44:04 -0400 Subject: [PATCH 107/378] test(file): migrate fsmonitor tests to Effect runner (#27099) --- packages/opencode/test/file/fsmonitor.test.ts | 103 +++++++++--------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/packages/opencode/test/file/fsmonitor.test.ts b/packages/opencode/test/file/fsmonitor.test.ts index f345cd085..3e025825b 100644 --- a/packages/opencode/test/file/fsmonitor.test.ts +++ b/packages/opencode/test/file/fsmonitor.test.ts @@ -3,67 +3,68 @@ import { describe, expect, test } from "bun:test" import { Effect } from "effect" import fs from "fs/promises" import path from "path" -import { File } from "../../src/file" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" -import { provideInstance, tmpdir } from "../fixture/fixture" -const run = (eff: Effect.Effect) => - Effect.runPromise(provideInstance(Instance.directory)(eff.pipe(Effect.provide(File.defaultLayer)))) -const status = () => run(File.Service.use((svc) => svc.status())) -const read = (file: string) => run(File.Service.use((svc) => svc.read(file))) - -const wintest = process.platform === "win32" ? test : test.skip +const it = process.platform === "win32" ? (await import("../lib/effect")).testEffect((await import("../../src/file")).File.defaultLayer) : undefined describe("file fsmonitor", () => { - wintest("status does not start fsmonitor for readonly git checks", async () => { - await using tmp = await tmpdir({ git: true }) - const target = path.join(tmp.path, "tracked.txt") + if (!it) { + test.skip("status does not start fsmonitor for readonly git checks", () => {}) + test.skip("read does not start fsmonitor for git diffs", () => {}) + return + } + + it.instance( + "status does not start fsmonitor for readonly git checks", + () => + Effect.gen(function* () { + const { File } = yield* Effect.promise(() => import("../../src/file")) + const { TestInstance } = yield* Effect.promise(() => import("../fixture/fixture")) + const directory = (yield* TestInstance).directory + const target = path.join(directory, "tracked.txt") - await fs.writeFile(target, "base\n") - await $`git add tracked.txt`.cwd(tmp.path).quiet() - await $`git commit -m init`.cwd(tmp.path).quiet() - await $`git config core.fsmonitor true`.cwd(tmp.path).quiet() - await $`git fsmonitor--daemon stop`.cwd(tmp.path).quiet().nothrow() - await fs.writeFile(target, "next\n") - await fs.writeFile(path.join(tmp.path, "new.txt"), "new\n") + yield* Effect.promise(() => fs.writeFile(target, "base\n")) + yield* Effect.promise(() => $`git add tracked.txt`.cwd(directory).quiet()) + yield* Effect.promise(() => $`git commit -m init`.cwd(directory).quiet()) + yield* Effect.promise(() => $`git config core.fsmonitor true`.cwd(directory).quiet()) + yield* Effect.promise(() => $`git fsmonitor--daemon stop`.cwd(directory).quiet().nothrow()) + yield* Effect.promise(() => fs.writeFile(target, "next\n")) + yield* Effect.promise(() => fs.writeFile(path.join(directory, "new.txt"), "new\n")) - const before = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow() - expect(before.exitCode).not.toBe(0) + const before = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow()) + expect(before.exitCode).not.toBe(0) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await status() - }, - }) + yield* File.Service.use((svc) => svc.status()) - const after = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow() - expect(after.exitCode).not.toBe(0) - }) + const after = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow()) + expect(after.exitCode).not.toBe(0) + }), + { git: true }, + ) - wintest("read does not start fsmonitor for git diffs", async () => { - await using tmp = await tmpdir({ git: true }) - const target = path.join(tmp.path, "tracked.txt") + it.instance( + "read does not start fsmonitor for git diffs", + () => + Effect.gen(function* () { + const { File } = yield* Effect.promise(() => import("../../src/file")) + const { TestInstance } = yield* Effect.promise(() => import("../fixture/fixture")) + const directory = (yield* TestInstance).directory + const target = path.join(directory, "tracked.txt") - await fs.writeFile(target, "base\n") - await $`git add tracked.txt`.cwd(tmp.path).quiet() - await $`git commit -m init`.cwd(tmp.path).quiet() - await $`git config core.fsmonitor true`.cwd(tmp.path).quiet() - await $`git fsmonitor--daemon stop`.cwd(tmp.path).quiet().nothrow() - await fs.writeFile(target, "next\n") + yield* Effect.promise(() => fs.writeFile(target, "base\n")) + yield* Effect.promise(() => $`git add tracked.txt`.cwd(directory).quiet()) + yield* Effect.promise(() => $`git commit -m init`.cwd(directory).quiet()) + yield* Effect.promise(() => $`git config core.fsmonitor true`.cwd(directory).quiet()) + yield* Effect.promise(() => $`git fsmonitor--daemon stop`.cwd(directory).quiet().nothrow()) + yield* Effect.promise(() => fs.writeFile(target, "next\n")) - const before = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow() - expect(before.exitCode).not.toBe(0) + const before = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow()) + expect(before.exitCode).not.toBe(0) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await read("tracked.txt") - }, - }) + yield* File.Service.use((svc) => svc.read("tracked.txt")) - const after = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow() - expect(after.exitCode).not.toBe(0) - }) + const after = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow()) + expect(after.exitCode).not.toBe(0) + }), + { git: true }, + ) }) From e8125e9b428cd68260074e0de087cb1ae3ec3cf1 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 12:45:05 -0400 Subject: [PATCH 108/378] test(server): migrate session list tests to Effect runner (#27101) --- .../opencode/test/server/session-list.test.ts | 358 +++++++++--------- 1 file changed, 170 insertions(+), 188 deletions(-) diff --git a/packages/opencode/test/server/session-list.test.ts b/packages/opencode/test/server/session-list.test.ts index 20478dde8..7a4eb61a4 100644 --- a/packages/opencode/test/server/session-list.test.ts +++ b/packages/opencode/test/server/session-list.test.ts @@ -1,33 +1,25 @@ -import { afterEach, describe, expect, test } from "bun:test" +import { afterEach, describe, expect } from "bun:test" import { Effect } from "effect" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { Session as SessionNs } from "@/session/session" import * as Log from "@opencode-ai/core/util/log" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, provideInstance, TestInstance } from "../fixture/fixture" import { Flag } from "@opencode-ai/core/flag/flag" import { mkdir } from "fs/promises" import path from "path" import { Database } from "@/storage/db" import { SessionTable } from "@/session/session.sql" import { eq } from "drizzle-orm" +import { testEffect } from "../lib/effect" void Log.init({ print: false }) const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES +const it = testEffect(SessionNs.defaultLayer) -function run(fx: Effect.Effect) { - return Effect.runPromise(fx.pipe(Effect.provide(SessionNs.defaultLayer))) -} - -const svc = { - ...SessionNs, - create(input?: SessionNs.CreateInput) { - return run(SessionNs.Service.use((svc) => svc.create(input))) - }, - list(input?: SessionNs.ListInput) { - return run(SessionNs.Service.use((svc) => svc.list(input))) - }, -} +const withSession = (input?: Parameters[0]) => + Effect.acquireRelease( + SessionNs.Service.use((session) => session.create(input)), + (created) => SessionNs.Service.use((session) => session.remove(created.id).pipe(Effect.ignore)), + ) afterEach(async () => { Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces @@ -35,205 +27,195 @@ afterEach(async () => { }) describe("session.list", () => { - test("does not filter by directory when directory is omitted", async () => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false - await using tmp = await tmpdir({ git: true }) - await mkdir(path.join(tmp.path, "packages", "opencode"), { recursive: true }) - await mkdir(path.join(tmp.path, "packages", "app"), { recursive: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const root = await svc.create({ title: "root" }) - - const parent = await WithInstance.provide({ - directory: path.join(tmp.path, "packages"), - fn: async () => svc.create({ title: "parent" }), - }) - const current = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "opencode"), - fn: async () => svc.create({ title: "current" }), - }) - const sibling = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "app"), - fn: async () => svc.create({ title: "sibling" }), - }) - - const ids = (await svc.list()).map((s) => s.id) + it.instance( + "does not filter by directory when directory is omitted", + () => + Effect.gen(function* () { + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false + const test = yield* TestInstance + yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode"), { recursive: true })) + yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) + + const root = yield* withSession({ title: "root" }) + const parent = yield* withSession({ title: "parent" }).pipe(provideInstance(path.join(test.directory, "packages"))) + const current = yield* withSession({ title: "current" }).pipe( + provideInstance(path.join(test.directory, "packages", "opencode")), + ) + const sibling = yield* withSession({ title: "sibling" }).pipe( + provideInstance(path.join(test.directory, "packages", "app")), + ) + + const ids = (yield* SessionNs.Service.use((session) => session.list())).map((session) => session.id) expect(ids).toContain(root.id) expect(ids).toContain(parent.id) expect(ids).toContain(current.id) expect(ids).toContain(sibling.id) - }, - }) - }) - - test("filters by directory when directory is provided", async () => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false - await using tmp = await tmpdir({ git: true }) - await mkdir(path.join(tmp.path, "packages", "opencode"), { recursive: true }) - await mkdir(path.join(tmp.path, "packages", "app"), { recursive: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const root = await svc.create({ title: "root" }) - - const parent = await WithInstance.provide({ - directory: path.join(tmp.path, "packages"), - fn: async () => svc.create({ title: "parent" }), - }) - const current = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "opencode"), - fn: async () => svc.create({ title: "current" }), - }) - const sibling = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "app"), - fn: async () => svc.create({ title: "sibling" }), - }) - - const ids = (await svc.list({ directory: path.join(tmp.path, "packages", "opencode") })).map((s) => s.id) + }), + { git: true }, + ) + + it.instance( + "filters by directory when directory is provided", + () => + Effect.gen(function* () { + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false + const test = yield* TestInstance + yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode"), { recursive: true })) + yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) + + const root = yield* withSession({ title: "root" }) + const parent = yield* withSession({ title: "parent" }).pipe(provideInstance(path.join(test.directory, "packages"))) + const current = yield* withSession({ title: "current" }).pipe( + provideInstance(path.join(test.directory, "packages", "opencode")), + ) + const sibling = yield* withSession({ title: "sibling" }).pipe( + provideInstance(path.join(test.directory, "packages", "app")), + ) + + const ids = ( + yield* SessionNs.Service.use((session) => + session.list({ directory: path.join(test.directory, "packages", "opencode") }), + ) + ).map((session) => session.id) expect(ids).not.toContain(root.id) expect(ids).not.toContain(parent.id) expect(ids).toContain(current.id) expect(ids).not.toContain(sibling.id) - }, - }) - }) - - test("filters by path and ignores directory when path is provided", async () => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false - await using tmp = await tmpdir({ git: true }) - await mkdir(path.join(tmp.path, "packages", "opencode", "src", "deep"), { recursive: true }) - await mkdir(path.join(tmp.path, "packages", "app"), { recursive: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const parent = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "opencode"), - fn: async () => svc.create({ title: "parent" }), - }) - const current = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "opencode", "src"), - fn: async () => svc.create({ title: "current" }), - }) - const deeper = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "opencode", "src", "deep"), - fn: async () => svc.create({ title: "deeper" }), - }) - const sibling = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "app"), - fn: async () => svc.create({ title: "sibling" }), - }) + }), + { git: true }, + ) + + it.instance( + "filters by path and ignores directory when path is provided", + () => + Effect.gen(function* () { + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false + const test = yield* TestInstance + yield* Effect.promise(() => + mkdir(path.join(test.directory, "packages", "opencode", "src", "deep"), { recursive: true }), + ) + yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) + + const parent = yield* withSession({ title: "parent" }).pipe( + provideInstance(path.join(test.directory, "packages", "opencode")), + ) + const current = yield* withSession({ title: "current" }).pipe( + provideInstance(path.join(test.directory, "packages", "opencode", "src")), + ) + const deeper = yield* withSession({ title: "deeper" }).pipe( + provideInstance(path.join(test.directory, "packages", "opencode", "src", "deep")), + ) + const sibling = yield* withSession({ title: "sibling" }).pipe( + provideInstance(path.join(test.directory, "packages", "app")), + ) const pathIDs = ( - await svc.list({ - directory: path.join(tmp.path, "packages", "app"), - path: "packages/opencode/src", - }) - ).map((s) => s.id) + yield* SessionNs.Service.use((session) => + session.list({ + directory: path.join(test.directory, "packages", "app"), + path: "packages/opencode/src", + }), + ) + ).map((session) => session.id) expect(pathIDs).not.toContain(parent.id) expect(pathIDs).toContain(current.id) expect(pathIDs).toContain(deeper.id) expect(pathIDs).not.toContain(sibling.id) - }, - }) - }) - - test("falls back to directory when filtering legacy sessions without path", async () => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false - await using tmp = await tmpdir({ git: true }) - await mkdir(path.join(tmp.path, "packages", "opencode", "src"), { recursive: true }) - await mkdir(path.join(tmp.path, "packages", "app"), { recursive: true }) - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const current = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "opencode", "src"), - fn: async () => svc.create({ title: "legacy-current" }), - }) - const sibling = await WithInstance.provide({ - directory: path.join(tmp.path, "packages", "app"), - fn: async () => svc.create({ title: "legacy-sibling" }), - }) - - Database.use((db) => db.update(SessionTable).set({ path: null }).where(eq(SessionTable.id, current.id)).run()) - Database.use((db) => db.update(SessionTable).set({ path: null }).where(eq(SessionTable.id, sibling.id)).run()) + }), + { git: true }, + ) + + it.instance( + "falls back to directory when filtering legacy sessions without path", + () => + Effect.gen(function* () { + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false + const test = yield* TestInstance + yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode", "src"), { recursive: true })) + yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) + + const current = yield* withSession({ title: "legacy-current" }).pipe( + provideInstance(path.join(test.directory, "packages", "opencode", "src")), + ) + const sibling = yield* withSession({ title: "legacy-sibling" }).pipe( + provideInstance(path.join(test.directory, "packages", "app")), + ) + + yield* Effect.sync(() => + Database.use((db) => db.update(SessionTable).set({ path: null }).where(eq(SessionTable.id, current.id)).run()), + ) + yield* Effect.sync(() => + Database.use((db) => db.update(SessionTable).set({ path: null }).where(eq(SessionTable.id, sibling.id)).run()), + ) const pathIDs = ( - await svc.list({ - directory: path.join(tmp.path, "packages", "opencode", "src"), - path: "packages/opencode/src", - }) - ).map((s) => s.id) + yield* SessionNs.Service.use((session) => + session.list({ + directory: path.join(test.directory, "packages", "opencode", "src"), + path: "packages/opencode/src", + }), + ) + ).map((session) => session.id) expect(pathIDs).toContain(current.id) expect(pathIDs).not.toContain(sibling.id) - }, - }) - }) + }), + { git: true }, + ) - test("filters root sessions", async () => { - await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const root = await svc.create({ title: "root-session" }) - const child = await svc.create({ title: "child-session", parentID: root.id }) + it.instance( + "filters root sessions", + () => + Effect.gen(function* () { + const root = yield* withSession({ title: "root-session" }) + const child = yield* withSession({ title: "child-session", parentID: root.id }) - const sessions = await svc.list({ roots: true }) - const ids = sessions.map((s) => s.id) + const sessions = yield* SessionNs.Service.use((session) => session.list({ roots: true })) + const ids = sessions.map((session) => session.id) expect(ids).toContain(root.id) expect(ids).not.toContain(child.id) - }, - }) - }) - - test("filters by start time", async () => { - await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await svc.create({ title: "new-session" }) - const futureStart = Date.now() + 86400000 - - const sessions = await svc.list({ start: futureStart }) + }), + { git: true }, + ) + + it.instance( + "filters by start time", + () => + Effect.gen(function* () { + yield* withSession({ title: "new-session" }) + const sessions = yield* SessionNs.Service.use((session) => session.list({ start: Date.now() + 86400000 })) expect(sessions.length).toBe(0) - }, - }) - }) + }), + { git: true }, + ) - test("filters by search term", async () => { - await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await svc.create({ title: "unique-search-term-abc" }) - await svc.create({ title: "other-session-xyz" }) + it.instance( + "filters by search term", + () => + Effect.gen(function* () { + yield* withSession({ title: "unique-search-term-abc" }) + yield* withSession({ title: "other-session-xyz" }) - const sessions = await svc.list({ search: "unique-search" }) - const titles = sessions.map((s) => s.title) + const sessions = yield* SessionNs.Service.use((session) => session.list({ search: "unique-search" })) + const titles = sessions.map((session) => session.title) expect(titles).toContain("unique-search-term-abc") expect(titles).not.toContain("other-session-xyz") - }, - }) - }) - - test("respects limit parameter", async () => { - await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await svc.create({ title: "session-1" }) - await svc.create({ title: "session-2" }) - await svc.create({ title: "session-3" }) - - const sessions = await svc.list({ limit: 2 }) + }), + { git: true }, + ) + + it.instance( + "respects limit parameter", + () => + Effect.gen(function* () { + yield* withSession({ title: "session-1" }) + yield* withSession({ title: "session-2" }) + yield* withSession({ title: "session-3" }) + + const sessions = yield* SessionNs.Service.use((session) => session.list({ limit: 2 })) expect(sessions.length).toBe(2) - }, - }) - }) + }), + { git: true }, + ) }) From 0ce614a280aab5ceb26a449577ba88c9ec9941cb Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 16:46:35 +0000 Subject: [PATCH 109/378] chore: generate --- packages/opencode/test/file/fsmonitor.test.ts | 5 +- .../opencode/test/file/path-traversal.test.ts | 62 +++++++++++-------- .../opencode/test/server/session-list.test.ts | 56 +++++++++-------- .../opencode/test/tool/apply_patch.test.ts | 11 +++- 4 files changed, 77 insertions(+), 57 deletions(-) diff --git a/packages/opencode/test/file/fsmonitor.test.ts b/packages/opencode/test/file/fsmonitor.test.ts index 3e025825b..b8d3bd605 100644 --- a/packages/opencode/test/file/fsmonitor.test.ts +++ b/packages/opencode/test/file/fsmonitor.test.ts @@ -4,7 +4,10 @@ import { Effect } from "effect" import fs from "fs/promises" import path from "path" -const it = process.platform === "win32" ? (await import("../lib/effect")).testEffect((await import("../../src/file")).File.defaultLayer) : undefined +const it = + process.platform === "win32" + ? (await import("../lib/effect")).testEffect((await import("../../src/file")).File.defaultLayer) + : undefined describe("file fsmonitor", () => { if (!it) { diff --git a/packages/opencode/test/file/path-traversal.test.ts b/packages/opencode/test/file/path-traversal.test.ts index 28bd34978..336f214d1 100644 --- a/packages/opencode/test/file/path-traversal.test.ts +++ b/packages/opencode/test/file/path-traversal.test.ts @@ -106,13 +106,15 @@ describe("File.list path traversal protection", () => { }) describe("containsPath", () => { - it.instance("returns true for path inside directory", () => - Effect.gen(function* () { - const test = yield* TestInstance - const ctx = yield* InstanceState.context - expect(containsPath(path.join(test.directory, "foo.txt"), ctx)).toBe(true) - expect(containsPath(path.join(test.directory, "src", "file.ts"), ctx)).toBe(true) - }), + it.instance( + "returns true for path inside directory", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceState.context + expect(containsPath(path.join(test.directory, "foo.txt"), ctx)).toBe(true) + expect(containsPath(path.join(test.directory, "src", "file.ts"), ctx)).toBe(true) + }), { git: true }, ) @@ -135,32 +137,38 @@ describe("containsPath", () => { { git: true }, ) - it.instance("returns false for path outside both directory and worktree", () => - Effect.gen(function* () { - const ctx = yield* InstanceState.context - expect(containsPath("/etc/passwd", ctx)).toBe(false) - expect(containsPath("/tmp/other-project", ctx)).toBe(false) - }), + it.instance( + "returns false for path outside both directory and worktree", + () => + Effect.gen(function* () { + const ctx = yield* InstanceState.context + expect(containsPath("/etc/passwd", ctx)).toBe(false) + expect(containsPath("/tmp/other-project", ctx)).toBe(false) + }), { git: true }, ) - it.instance("returns false for path with .. escaping worktree", () => - Effect.gen(function* () { - const test = yield* TestInstance - const ctx = yield* InstanceState.context - expect(containsPath(path.join(test.directory, "..", "escape.txt"), ctx)).toBe(false) - }), + it.instance( + "returns false for path with .. escaping worktree", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceState.context + expect(containsPath(path.join(test.directory, "..", "escape.txt"), ctx)).toBe(false) + }), { git: true }, ) - it.instance("handles directory === worktree (running from repo root)", () => - Effect.gen(function* () { - const test = yield* TestInstance - const ctx = yield* InstanceState.context - expect(ctx.directory).toBe(ctx.worktree) - expect(containsPath(path.join(test.directory, "file.txt"), ctx)).toBe(true) - expect(containsPath("/etc/passwd", ctx)).toBe(false) - }), + it.instance( + "handles directory === worktree (running from repo root)", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceState.context + expect(ctx.directory).toBe(ctx.worktree) + expect(containsPath(path.join(test.directory, "file.txt"), ctx)).toBe(true) + expect(containsPath("/etc/passwd", ctx)).toBe(false) + }), { git: true }, ) diff --git a/packages/opencode/test/server/session-list.test.ts b/packages/opencode/test/server/session-list.test.ts index 7a4eb61a4..e5dc72546 100644 --- a/packages/opencode/test/server/session-list.test.ts +++ b/packages/opencode/test/server/session-list.test.ts @@ -37,7 +37,9 @@ describe("session.list", () => { yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) const root = yield* withSession({ title: "root" }) - const parent = yield* withSession({ title: "parent" }).pipe(provideInstance(path.join(test.directory, "packages"))) + const parent = yield* withSession({ title: "parent" }).pipe( + provideInstance(path.join(test.directory, "packages")), + ) const current = yield* withSession({ title: "current" }).pipe( provideInstance(path.join(test.directory, "packages", "opencode")), ) @@ -64,7 +66,9 @@ describe("session.list", () => { yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) const root = yield* withSession({ title: "root" }) - const parent = yield* withSession({ title: "parent" }).pipe(provideInstance(path.join(test.directory, "packages"))) + const parent = yield* withSession({ title: "parent" }).pipe( + provideInstance(path.join(test.directory, "packages")), + ) const current = yield* withSession({ title: "current" }).pipe( provideInstance(path.join(test.directory, "packages", "opencode")), ) @@ -72,11 +76,9 @@ describe("session.list", () => { provideInstance(path.join(test.directory, "packages", "app")), ) - const ids = ( - yield* SessionNs.Service.use((session) => - session.list({ directory: path.join(test.directory, "packages", "opencode") }), - ) - ).map((session) => session.id) + const ids = (yield* SessionNs.Service.use((session) => + session.list({ directory: path.join(test.directory, "packages", "opencode") }), + )).map((session) => session.id) expect(ids).not.toContain(root.id) expect(ids).not.toContain(parent.id) expect(ids).toContain(current.id) @@ -109,14 +111,12 @@ describe("session.list", () => { provideInstance(path.join(test.directory, "packages", "app")), ) - const pathIDs = ( - yield* SessionNs.Service.use((session) => - session.list({ - directory: path.join(test.directory, "packages", "app"), - path: "packages/opencode/src", - }), - ) - ).map((session) => session.id) + const pathIDs = (yield* SessionNs.Service.use((session) => + session.list({ + directory: path.join(test.directory, "packages", "app"), + path: "packages/opencode/src", + }), + )).map((session) => session.id) expect(pathIDs).not.toContain(parent.id) expect(pathIDs).toContain(current.id) expect(pathIDs).toContain(deeper.id) @@ -131,7 +131,9 @@ describe("session.list", () => { Effect.gen(function* () { Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false const test = yield* TestInstance - yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode", "src"), { recursive: true })) + yield* Effect.promise(() => + mkdir(path.join(test.directory, "packages", "opencode", "src"), { recursive: true }), + ) yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true })) const current = yield* withSession({ title: "legacy-current" }).pipe( @@ -142,20 +144,22 @@ describe("session.list", () => { ) yield* Effect.sync(() => - Database.use((db) => db.update(SessionTable).set({ path: null }).where(eq(SessionTable.id, current.id)).run()), + Database.use((db) => + db.update(SessionTable).set({ path: null }).where(eq(SessionTable.id, current.id)).run(), + ), ) yield* Effect.sync(() => - Database.use((db) => db.update(SessionTable).set({ path: null }).where(eq(SessionTable.id, sibling.id)).run()), + Database.use((db) => + db.update(SessionTable).set({ path: null }).where(eq(SessionTable.id, sibling.id)).run(), + ), ) - const pathIDs = ( - yield* SessionNs.Service.use((session) => - session.list({ - directory: path.join(test.directory, "packages", "opencode", "src"), - path: "packages/opencode/src", - }), - ) - ).map((session) => session.id) + const pathIDs = (yield* SessionNs.Service.use((session) => + session.list({ + directory: path.join(test.directory, "packages", "opencode", "src"), + path: "packages/opencode/src", + }), + )).map((session) => session.id) expect(pathIDs).toContain(current.id) expect(pathIDs).not.toContain(sibling.id) }), diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index 190254866..be5754f3b 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -212,7 +212,8 @@ describe("tool.apply_patch freeform", () => { const target = path.join(test.directory, "example.cs") yield* writeText(target, `${bom}using System;\n\nclass Test {}\n`) - const patchText = "*** Begin Patch\n*** Update File: example.cs\n@@\n class Test {}\n+class Next {}\n*** End Patch" + const patchText = + "*** Begin Patch\n*** Update File: example.cs\n@@\n class Test {}\n+class Next {}\n*** End Patch" yield* execute({ patchText }, ctx) @@ -320,7 +321,10 @@ describe("tool.apply_patch freeform", () => { const { ctx } = makeCtx() const patchText = "*** Begin Patch\n*** Update File: missing.txt\n@@\n-nope\n+better\n*** End Patch" - yield* expectFailure(execute({ patchText }, ctx), "apply_patch verification failed: Failed to read file to update") + yield* expectFailure( + execute({ patchText }, ctx), + "apply_patch verification failed: Failed to read file to update", + ) }), ) @@ -518,7 +522,8 @@ EOF` // Patch uses ASCII equivalents - should match via normalized pass // The replacement uses ASCII quotes from the patch (not preserving Unicode) - const patchText = '*** Begin Patch\n*** Update File: unicode.txt\n@@\n-He said "hello"\n+He said "hi"\n*** End Patch' + const patchText = + '*** Begin Patch\n*** Update File: unicode.txt\n@@\n-He said "hello"\n+He said "hi"\n*** End Patch' yield* execute({ patchText }, ctx) // Result has ASCII quotes because that's what the patch specifies From 5a4596c879a69aa20343060e5b5d5efeedfbbc0e Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Tue, 12 May 2026 12:50:32 -0400 Subject: [PATCH 110/378] core: Wait 3 days before installing new package versions to reduce supply chain risk --- bunfig.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bunfig.toml b/bunfig.toml index 36a21d933..363579bbf 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,6 +1,7 @@ [install] exact = true +# Only install newly resolved package versions published at least 3 days ago. +minimumReleaseAge = 259200 [test] root = "./do-not-run-tests-from-root" - From 53a3f95088941aa0d3979af90b28b071c40fd866 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 12:58:50 -0400 Subject: [PATCH 111/378] Make core fn Zod import type-only (#27103) --- packages/core/src/util/fn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/util/fn.ts b/packages/core/src/util/fn.ts index 9efe4622f..828baf3bd 100644 --- a/packages/core/src/util/fn.ts +++ b/packages/core/src/util/fn.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import type { z } from "zod" export function fn(schema: T, cb: (input: z.infer) => Result) { const result = (input: z.infer) => { From 2b9af91568d6c7e65f644e52be295a955bf9a7ca Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 13:08:57 -0400 Subject: [PATCH 112/378] Remove Zod from core log (#27102) --- packages/core/src/util/log.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/util/log.ts b/packages/core/src/util/log.ts index e1962aed4..83060b29c 100644 --- a/packages/core/src/util/log.ts +++ b/packages/core/src/util/log.ts @@ -4,11 +4,14 @@ import path from "path" import fs from "fs/promises" import { createWriteStream } from "fs" import * as Global from "../global" -import z from "zod" +import { Schema } from "effect" import { Glob } from "./glob" -export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" }) -export type Level = z.infer +export const Level = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({ + identifier: "LogLevel", + description: "Log level", +}) +export type Level = Schema.Schema.Type const levelPriority: Record = { DEBUG: 0, From bc4fdb837023a1b293ebf69f9729febd0ca13353 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 13:09:23 -0400 Subject: [PATCH 113/378] Remove unused app ID schema (#27105) --- packages/app/src/utils/id.ts | 6 ------ .../.storybook/mocks/app/context/global-sync.ts | 13 +++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/app/src/utils/id.ts b/packages/app/src/utils/id.ts index fa27cf4c5..dba7a8d95 100644 --- a/packages/app/src/utils/id.ts +++ b/packages/app/src/utils/id.ts @@ -1,5 +1,3 @@ -import z from "zod" - const prefixes = { session: "ses", message: "msg", @@ -15,10 +13,6 @@ let counter = 0 type Prefix = keyof typeof prefixes export namespace Identifier { - export function schema(prefix: Prefix) { - return z.string().startsWith(prefixes[prefix]) - } - export function ascending(prefix: Prefix, given?: string) { return generateID(prefix, false, given) } diff --git a/packages/storybook/.storybook/mocks/app/context/global-sync.ts b/packages/storybook/.storybook/mocks/app/context/global-sync.ts index 2eb134d37..92622c04a 100644 --- a/packages/storybook/.storybook/mocks/app/context/global-sync.ts +++ b/packages/storybook/.storybook/mocks/app/context/global-sync.ts @@ -40,3 +40,16 @@ export function useGlobalSync() { }, } } + +export function useQueryOptions() { + return { + agents: (directory: string) => ({ + queryKey: [directory, "agents"], + queryFn: async () => [], + }), + providers: (directory: string | null) => ({ + queryKey: [directory, "providers"], + queryFn: async () => provider, + }), + } +} From 6b950b666a4cf4d9a238cd4727bd0d39ffeed063 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 13:51:08 -0400 Subject: [PATCH 114/378] Remove Zod from core dependencies (#27107) --- bun.lock | 1 - packages/core/package.json | 3 +-- packages/core/src/util/fn.ts | 11 ----------- packages/enterprise/src/core/share.ts | 5 ++++- 4 files changed, 5 insertions(+), 15 deletions(-) delete mode 100644 packages/core/src/util/fn.ts diff --git a/bun.lock b/bun.lock index 4268e5fb7..9ab93237d 100644 --- a/bun.lock +++ b/bun.lock @@ -214,7 +214,6 @@ "npm-package-arg": "13.0.2", "semver": "^7.6.3", "xdg-basedir": "5.1.0", - "zod": "catalog:", }, "devDependencies": { "@tsconfig/bun": "catalog:", diff --git a/packages/core/package.json b/packages/core/package.json index e2ffa31d8..6bcef68dc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -41,8 +41,7 @@ "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "semver": "^7.6.3", - "xdg-basedir": "5.1.0", - "zod": "catalog:" + "xdg-basedir": "5.1.0" }, "overrides": { "drizzle-orm": "catalog:" diff --git a/packages/core/src/util/fn.ts b/packages/core/src/util/fn.ts deleted file mode 100644 index 828baf3bd..000000000 --- a/packages/core/src/util/fn.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { z } from "zod" - -export function fn(schema: T, cb: (input: z.infer) => Result) { - const result = (input: z.infer) => { - const parsed = schema.parse(input) - return cb(parsed) - } - result.force = (input: z.infer) => cb(input) - result.schema = schema - return result -} diff --git a/packages/enterprise/src/core/share.ts b/packages/enterprise/src/core/share.ts index fb8cd3029..a39171462 100644 --- a/packages/enterprise/src/core/share.ts +++ b/packages/enterprise/src/core/share.ts @@ -1,9 +1,12 @@ import { Message, Model, Part, Session, SnapshotFileDiff } from "@opencode-ai/sdk/v2" -import { fn } from "@opencode-ai/core/util/fn" import { iife } from "@opencode-ai/core/util/iife" import z from "zod" import { Storage } from "./storage" +function fn(schema: T, cb: (input: z.infer) => Result) { + return (input: z.infer) => cb(schema.parse(input)) +} + export namespace Share { export const Info = z.object({ id: z.string(), From fda37b3609954a0f09991f1c76d9238044381e02 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:02:12 -0400 Subject: [PATCH 115/378] Remove Zod from app global SDK (#27111) --- bun.lock | 1 - packages/app/package.json | 3 +-- packages/app/src/context/global-sdk.tsx | 8 +++----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index 9ab93237d..5da588910 100644 --- a/bun.lock +++ b/bun.lock @@ -65,7 +65,6 @@ "solid-list": "catalog:", "tailwindcss": "catalog:", "virtua": "catalog:", - "zod": "catalog:", }, "devDependencies": { "@happy-dom/global-registrator": "20.0.11", diff --git a/packages/app/package.json b/packages/app/package.json index 9eb408372..86999ed45 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -73,7 +73,6 @@ "solid-js": "catalog:", "solid-list": "catalog:", "tailwindcss": "catalog:", - "virtua": "catalog:", - "zod": "catalog:" + "virtua": "catalog:" } } diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx index e53d60d5a..001b90b42 100644 --- a/packages/app/src/context/global-sdk.tsx +++ b/packages/app/src/context/global-sdk.tsx @@ -3,15 +3,13 @@ import { createSimpleContext } from "@opencode-ai/ui/context" import { createGlobalEmitter } from "@solid-primitives/event-bus" import { makeEventListener } from "@solid-primitives/event-listener" import { batch, onCleanup, onMount } from "solid-js" -import z from "zod" import { createSdkForServer } from "@/utils/server" import { useLanguage } from "./language" import { usePlatform } from "./platform" import { useServer } from "./server" -const abortError = z.object({ - name: z.literal("AbortError"), -}) +const isAbortError = (error: unknown) => + error !== null && typeof error === "object" && "name" in error && error.name === "AbortError" export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({ name: "GlobalSDK", @@ -103,7 +101,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo let streamErrorLogged = false const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) - const aborted = (error: unknown) => abortError.safeParse(error).success + const aborted = isAbortError let attempt: AbortController | undefined let run: Promise | undefined From 3974520742e1f2a64209429b4e8c6c845f34f6b5 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:09:00 -0400 Subject: [PATCH 116/378] Migrate UI cancel error to tagged error (#27112) --- packages/opencode/src/cli/error.ts | 4 ++-- packages/opencode/src/cli/ui.ts | 3 +-- packages/opencode/test/cli/error.test.ts | 5 +++++ packages/opencode/test/util/error.test.ts | 8 ++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index 628aa9569..6fd7b573e 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -78,8 +78,8 @@ export function FormatError(input: unknown) { ].join("\n") } - // UICancelledError: void (no data) - if (NamedError.hasName(input, "UICancelledError")) { + // UICancelledError: user cancelled an interactive CLI prompt + if (isTaggedError(input, "UICancelledError") || NamedError.hasName(input, "UICancelledError")) { return "" } } diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts index 69e04b925..6ad6495cf 100644 --- a/packages/opencode/src/cli/ui.ts +++ b/packages/opencode/src/cli/ui.ts @@ -1,5 +1,4 @@ import { EOL } from "os" -import { NamedError } from "@opencode-ai/core/util/error" import { Schema } from "effect" import { logo as glyphs } from "./logo" @@ -10,7 +9,7 @@ const wordmark = [ `▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`, ] -export const CancelledError = NamedError.create("UICancelledError", Schema.optional(Schema.Void)) +export class CancelledError extends Schema.TaggedErrorClass()("UICancelledError", {}) {} export const Style = { TEXT_HIGHLIGHT: "\x1b[96m", diff --git a/packages/opencode/test/cli/error.test.ts b/packages/opencode/test/cli/error.test.ts index 6af2633ce..b4d1dbeda 100644 --- a/packages/opencode/test/cli/error.test.ts +++ b/packages/opencode/test/cli/error.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test" import { AccountTransportError } from "../../src/account/schema" import { FormatError } from "../../src/cli/error" +import { UI } from "../../src/cli/ui" describe("cli.error", () => { test("formats account transport errors clearly", () => { @@ -15,4 +16,8 @@ describe("cli.error", () => { expect(formatted).toContain("This failed before the server returned an HTTP response.") expect(formatted).toContain("Check your network, proxy, or VPN configuration and try again.") }) + + test("formats cancelled UI errors as empty output", () => { + expect(FormatError(new UI.CancelledError())).toBe("") + }) }) diff --git a/packages/opencode/test/util/error.test.ts b/packages/opencode/test/util/error.test.ts index 8d077b1f2..fdb559a23 100644 --- a/packages/opencode/test/util/error.test.ts +++ b/packages/opencode/test/util/error.test.ts @@ -1,8 +1,6 @@ import { describe, expect, test } from "bun:test" -import { Schema } from "effect" import { NamedError } from "@opencode-ai/core/util/error" import { errorData, errorFormat, errorMessage } from "../../src/util/error" -import { UI } from "../../src/cli/ui" import { MessageError } from "../../src/session/message-error" describe("util.error", () => { @@ -60,9 +58,7 @@ describe("util.error", () => { expect(error.toObject()).toEqual({ name: "ProviderAuthError", data: { providerID: "anthropic", message: "boom" } }) }) - test("void named errors accept JSON without data", () => { - const serialized = JSON.parse(JSON.stringify(new UI.CancelledError(undefined).toObject())) - - expect(Schema.decodeUnknownOption(UI.CancelledError.Schema)(serialized)._tag).toBe("Some") + test("named errors without fields serialize data", () => { + expect(new MessageError.OutputLengthError({}).toObject()).toEqual({ name: "MessageOutputLengthError", data: {} }) }) }) From 822eec0d62468cb2927529ba1e706e00c407db54 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:22:56 -0400 Subject: [PATCH 117/378] Fix runner cancel completion (#27115) --- packages/opencode/src/effect/runner.ts | 2 +- packages/opencode/test/effect/runner.test.ts | 39 +++++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/opencode/src/effect/runner.ts b/packages/opencode/src/effect/runner.ts index 1e7d4c296..5d7e8778d 100644 --- a/packages/opencode/src/effect/runner.ts +++ b/packages/opencode/src/effect/runner.ts @@ -181,7 +181,7 @@ export const make = ( return [ Effect.gen(function* () { yield* Fiber.interrupt(st.run.fiber) - yield* Deferred.await(st.run.done).pipe(Effect.exit, Effect.asVoid) + yield* Deferred.fail(st.run.done, new Cancelled()).pipe(Effect.asVoid) yield* idleIfCurrent() }), { _tag: "Idle" } as const, diff --git a/packages/opencode/test/effect/runner.test.ts b/packages/opencode/test/effect/runner.test.ts index c37cb276b..3030ca64e 100644 --- a/packages/opencode/test/effect/runner.test.ts +++ b/packages/opencode/test/effect/runner.test.ts @@ -3,6 +3,11 @@ import { Deferred, Effect, Exit, Fiber, Latch, Ref, Scope } from "effect" import { Runner } from "@/effect/runner" import { it } from "../lib/effect" +const waitForState = (runner: Runner.Runner, tag: Runner.State["_tag"]) => + Effect.gen(function* () { + while (runner.state._tag !== tag) yield* Effect.yieldNow + }).pipe(Effect.timeout("1 second")) + describe("Runner", () => { // --- ensureRunning semantics --- @@ -152,7 +157,7 @@ describe("Runner", () => { const s = yield* Scope.Scope const runner = Runner.make(s, { onInterrupt: Effect.succeed("fallback") }) const fiber = yield* runner.ensureRunning(Effect.never.pipe(Effect.as("never"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Running") yield* runner.cancel @@ -169,9 +174,9 @@ describe("Runner", () => { const runner = Runner.make(s, { onInterrupt: Effect.succeed("fallback") }) const a = yield* runner.ensureRunning(Effect.never.pipe(Effect.as("x"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Running") const b = yield* runner.ensureRunning(Effect.succeed("y")).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* Effect.yieldNow yield* runner.cancel @@ -189,7 +194,7 @@ describe("Runner", () => { const s = yield* Scope.Scope const runner = Runner.make(s) const fiber = yield* runner.ensureRunning(Effect.never.pipe(Effect.as("x"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Running") yield* runner.cancel yield* Fiber.await(fiber) @@ -215,7 +220,7 @@ describe("Runner", () => { ) const a = yield* runner.ensureRunning(first).pipe(Effect.exit, Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Running") const stop = yield* runner.cancel.pipe(Effect.forkChild) yield* Deferred.await(hit).pipe(Effect.timeout("250 millis")) @@ -293,7 +298,7 @@ describe("Runner", () => { const gate = yield* Deferred.make() const sh = yield* runner.startShell(Deferred.await(gate).pipe(Effect.as("first"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Shell") const exit = yield* runner.startShell(Effect.succeed("second")).pipe(Effect.exit) expect(Exit.isFailure(exit)).toBe(true) @@ -314,7 +319,7 @@ describe("Runner", () => { }) const sh = yield* runner.startShell(Effect.never.pipe(Effect.as("aborted"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Shell") const exit = yield* runner.startShell(Effect.succeed("second")).pipe(Effect.exit) expect(Exit.isFailure(exit)).toBe(true) @@ -333,7 +338,7 @@ describe("Runner", () => { const gate = yield* Deferred.make() const sh = yield* runner.startShell(Deferred.await(gate).pipe(Effect.as("ignored"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Shell") const stop = yield* runner.cancel.pipe(Effect.forkChild) const stopExit = yield* Fiber.await(stop).pipe(Effect.timeout("250 millis")) @@ -380,11 +385,11 @@ describe("Runner", () => { const gate = yield* Deferred.make() const sh = yield* runner.startShell(Deferred.await(gate).pipe(Effect.as("shell-result"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Shell") expect(runner.state._tag).toBe("Shell") const run = yield* runner.ensureRunning(Effect.succeed("run-result")).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "ShellThenRun") expect(runner.state._tag).toBe("ShellThenRun") yield* Deferred.succeed(gate, undefined) @@ -406,7 +411,7 @@ describe("Runner", () => { const gate = yield* Deferred.make() const sh = yield* runner.startShell(Deferred.await(gate).pipe(Effect.as("shell"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Shell") const work = Effect.gen(function* () { yield* Ref.update(calls, (n) => n + 1) @@ -414,7 +419,7 @@ describe("Runner", () => { }) const a = yield* runner.ensureRunning(work).pipe(Effect.forkChild) const b = yield* runner.ensureRunning(work).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "ShellThenRun") yield* Deferred.succeed(gate, undefined) yield* Fiber.await(sh) @@ -433,10 +438,10 @@ describe("Runner", () => { const runner = Runner.make(s) const sh = yield* runner.startShell(Effect.never.pipe(Effect.as("aborted"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Shell") const run = yield* runner.ensureRunning(Effect.succeed("y")).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "ShellThenRun") expect(runner.state._tag).toBe("ShellThenRun") yield* runner.cancel @@ -472,7 +477,7 @@ describe("Runner", () => { onIdle: Ref.update(count, (n) => n + 1), }) const fiber = yield* runner.ensureRunning(Effect.never.pipe(Effect.as("x"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Running") yield* runner.cancel yield* Fiber.await(fiber) expect(yield* Ref.get(count)).toBeGreaterThanOrEqual(1) @@ -502,7 +507,7 @@ describe("Runner", () => { const gate = yield* Deferred.make() const fiber = yield* runner.ensureRunning(Deferred.await(gate).pipe(Effect.as("ok"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Running") expect(runner.busy).toBe(true) yield* Deferred.succeed(gate, undefined) @@ -519,7 +524,7 @@ describe("Runner", () => { const gate = yield* Deferred.make() const fiber = yield* runner.startShell(Deferred.await(gate).pipe(Effect.as("ok"))).pipe(Effect.forkChild) - yield* Effect.sleep("10 millis") + yield* waitForState(runner, "Shell") expect(runner.busy).toBe(true) yield* Deferred.succeed(gate, undefined) From a3714d4399bc7002a111519ad7c5bb9c97a11382 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 18:27:42 +0000 Subject: [PATCH 118/378] 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 33003919a..ce8cded23 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-Q9r1S15YL9LQK7DRhuOpw3Fxi24BPovEM995GZJayKw=", - "aarch64-linux": "sha256-C0rRTLnxxuuEkCBc3JZbkR66TUVwpcPFif3BU9GRAuA=", - "aarch64-darwin": "sha256-1HvalOO/pOkRlYH8CZ93psapt90C+pYzui1JCadBE1Q=", - "x86_64-darwin": "sha256-RrndyLWfhWm4mZ88XytFF2NI+ly8la550Z5LBN/g5u4=" + "x86_64-linux": "sha256-MUHog06sZEi6bXR1m8exdkjSNW9bHEv9bPQXACJ7SFw=", + "aarch64-linux": "sha256-3dwdZ3It++OsdGT8xMOQ10Arz8eeODp/LXOrI4DLEhY=", + "aarch64-darwin": "sha256-TmUPGDCewjsrT13npVH6B55J43NKKut67p/HgPJpQNM=", + "x86_64-darwin": "sha256-j8I7t3MZoUQUMFRWyaFO75TRbAw5TauSZAa4yKOHFMA=" } } From ec30ff9120ade9032c94f899e10274fd45d4ec93 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:49:30 -0400 Subject: [PATCH 119/378] test(agent): migrate agent tests to Effect runner (#27118) --- packages/opencode/test/agent/agent.test.ts | 1060 +++++++++----------- 1 file changed, 489 insertions(+), 571 deletions(-) diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index df68fdfdc..7fd489150 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -1,12 +1,15 @@ -import { afterEach, test, expect } from "bun:test" -import { Effect } from "effect" +import { afterEach, expect } from "bun:test" +import { Cause, Effect, Exit } from "effect" import path from "path" -import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture" -import { WithInstance } from "../../src/project/with-instance" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" import { Agent } from "../../src/agent/agent" import { Global } from "@opencode-ai/core/global" import { Flag } from "@opencode-ai/core/flag/flag" import { Permission } from "../../src/permission" +import { Truncate } from "../../src/tool/truncate" + +const it = testEffect(Agent.defaultLayer) // Helper to evaluate permission for a tool with wildcard pattern function evalPerm(agent: Agent.Info | undefined, permission: string): Permission.Action | undefined { @@ -14,211 +17,179 @@ function evalPerm(agent: Agent.Info | undefined, permission: string): Permission return Permission.evaluate(permission, "*", agent.permission).action } -function load(dir: string, fn: (svc: Agent.Interface) => Effect.Effect) { - return Effect.runPromise(provideInstance(dir)(Agent.Service.use(fn)).pipe(Effect.provide(Agent.defaultLayer))) +function load(fn: (svc: Agent.Interface) => Effect.Effect) { + return Agent.Service.use(fn) } -async function withExperimentalScout(enabled: boolean, fn: () => Promise) { - const original = Flag.OPENCODE_EXPERIMENTAL_SCOUT - Flag.OPENCODE_EXPERIMENTAL_SCOUT = enabled - try { - await fn() - } finally { - Flag.OPENCODE_EXPERIMENTAL_SCOUT = original - } +function withExperimentalScout(enabled: boolean, self: Effect.Effect) { + return Effect.acquireUseRelease( + Effect.sync(() => { + const original = Flag.OPENCODE_EXPERIMENTAL_SCOUT + Flag.OPENCODE_EXPERIMENTAL_SCOUT = enabled + return original + }), + () => self, + (original) => + Effect.sync(() => { + Flag.OPENCODE_EXPERIMENTAL_SCOUT = original + }), + ) } -afterEach(async () => { - await disposeAllInstances() -}) - -test("returns default native agents when no config", async () => { - await withExperimentalScout(false, async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const agents = await load(tmp.path, (svc) => svc.list()) - const names = agents.map((a) => a.name) - expect(names).toContain("build") - expect(names).toContain("plan") - expect(names).toContain("general") - expect(names).toContain("explore") - expect(names).not.toContain("scout") - expect(names).toContain("compaction") - expect(names).toContain("title") - expect(names).toContain("summary") - }, - }) - }) -}) - -test("build agent has correct default properties", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(build).toBeDefined() - expect(build?.mode).toBe("primary") - expect(build?.native).toBe(true) - expect(evalPerm(build, "edit")).toBe("allow") - expect(evalPerm(build, "bash")).toBe("allow") - expect(evalPerm(build, "repo_clone")).toBe("deny") - expect(evalPerm(build, "repo_overview")).toBe("deny") - }, - }) +const expectDefaultAgentError = Effect.fn("AgentTest.expectDefaultAgentError")(function* (message: string) { + const exit = yield* load((svc) => svc.defaultAgent()).pipe(Effect.exit) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) expect(Cause.pretty(exit.cause)).toContain(message) }) -test("plan agent denies edits except .opencode/plans/*", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const plan = await load(tmp.path, (svc) => svc.get("plan")) - expect(plan).toBeDefined() - // Wildcard is denied - expect(evalPerm(plan, "edit")).toBe("deny") - // But specific path is allowed - expect(Permission.evaluate("edit", ".opencode/plans/foo.md", plan!.permission).action).toBe("allow") - }, - }) -}) - -test("explore agent denies edit and write", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const explore = await load(tmp.path, (svc) => svc.get("explore")) - expect(explore).toBeDefined() - expect(explore?.mode).toBe("subagent") - expect(evalPerm(explore, "edit")).toBe("deny") - expect(evalPerm(explore, "write")).toBe("deny") - expect(evalPerm(explore, "todowrite")).toBe("deny") - }, - }) +afterEach(async () => { + await disposeAllInstances() }) -test("explore agent asks for external directories and allows whitelisted external paths", async () => { - const { Truncate } = await import("../../src/tool/truncate") - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const explore = await load(tmp.path, (svc) => svc.get("explore")) - expect(explore).toBeDefined() - expect(Permission.evaluate("external_directory", "/some/other/path", explore!.permission).action).toBe("ask") - expect(Permission.evaluate("external_directory", Truncate.GLOB, explore!.permission).action).toBe("allow") +it.instance("returns default native agents when no config", () => + withExperimentalScout( + false, + Effect.gen(function* () { + const agents = yield* load((svc) => svc.list()) + const names = agents.map((a) => a.name) + expect(names).toContain("build") + expect(names).toContain("plan") + expect(names).toContain("general") + expect(names).toContain("explore") + expect(names).not.toContain("scout") + expect(names).toContain("compaction") + expect(names).toContain("title") + expect(names).toContain("summary") + }), + ), +) + +it.instance("build agent has correct default properties", () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(build).toBeDefined() + expect(build?.mode).toBe("primary") + expect(build?.native).toBe(true) + expect(evalPerm(build, "edit")).toBe("allow") + expect(evalPerm(build, "bash")).toBe("allow") + expect(evalPerm(build, "repo_clone")).toBe("deny") + expect(evalPerm(build, "repo_overview")).toBe("deny") + }), +) + +it.instance("plan agent denies edits except .opencode/plans/*", () => + Effect.gen(function* () { + const plan = yield* load((svc) => svc.get("plan")) + expect(plan).toBeDefined() + // Wildcard is denied + expect(evalPerm(plan, "edit")).toBe("deny") + // But specific path is allowed + expect(Permission.evaluate("edit", ".opencode/plans/foo.md", plan!.permission).action).toBe("allow") + }), +) + +it.instance("explore agent denies edit and write", () => + Effect.gen(function* () { + const explore = yield* load((svc) => svc.get("explore")) + expect(explore).toBeDefined() + expect(explore?.mode).toBe("subagent") + expect(evalPerm(explore, "edit")).toBe("deny") + expect(evalPerm(explore, "write")).toBe("deny") + expect(evalPerm(explore, "todowrite")).toBe("deny") + }), +) + +it.instance("explore agent asks for external directories and allows whitelisted external paths", () => + Effect.gen(function* () { + const explore = yield* load((svc) => svc.get("explore")) + expect(explore).toBeDefined() + expect(Permission.evaluate("external_directory", "/some/other/path", explore!.permission).action).toBe("ask") + expect(Permission.evaluate("external_directory", Truncate.GLOB, explore!.permission).action).toBe("allow") + expect(Permission.evaluate("external_directory", path.join(Global.Path.tmp, "agent-work"), explore!.permission).action).toBe( + "allow", + ) + }), +) + +it.instance("scout agent allows repo cloning and repo cache reads", () => + withExperimentalScout( + true, + Effect.gen(function* () { + const scout = yield* load((svc) => svc.get("scout")) + expect(scout).toBeDefined() + expect(scout?.mode).toBe("subagent") + expect(evalPerm(scout, "repo_clone")).toBe("allow") + expect(evalPerm(scout, "repo_overview")).toBe("allow") + expect(evalPerm(scout, "edit")).toBe("deny") expect( - Permission.evaluate("external_directory", path.join(Global.Path.tmp, "agent-work"), explore!.permission).action, + Permission.evaluate( + "external_directory", + path.join(Global.Path.repos, "github.com", "owner", "repo", "README.md"), + scout!.permission, + ).action, ).toBe("allow") - }, - }) -}) - -test("scout agent allows repo cloning and repo cache reads", async () => { - await withExperimentalScout(true, async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const scout = await load(tmp.path, (svc) => svc.get("scout")) - expect(scout).toBeDefined() - expect(scout?.mode).toBe("subagent") - expect(evalPerm(scout, "repo_clone")).toBe("allow") - expect(evalPerm(scout, "repo_overview")).toBe("allow") - expect(evalPerm(scout, "edit")).toBe("deny") - expect( - Permission.evaluate( - "external_directory", - path.join(Global.Path.repos, "github.com", "owner", "repo", "README.md"), - scout!.permission, - ).action, - ).toBe("allow") - }, - }) - }) -}) - -test("reference config does not create subagents", async () => { - await withExperimentalScout(true, async () => { - await using tmp = await tmpdir({ - config: { - reference: { - effect: "github.com/effect/effect-smol", - effectFull: { - repository: "Effect-TS/effect", - branch: "main", - }, - localdocs: "../docs", - localdocsFull: { - path: "../local-docs", - }, - }, - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const agents = await load(tmp.path, (svc) => svc.list()) + }), + ), +) + +it.instance( + "reference config does not create subagents", + () => + withExperimentalScout( + true, + Effect.gen(function* () { + const agents = yield* load((svc) => svc.list()) const names = agents.map((agent) => agent.name) expect(names).toContain("scout") expect(names).not.toContain("effect") expect(names).not.toContain("effectFull") expect(names).not.toContain("localdocs") expect(names).not.toContain("localdocsFull") - }, - }) - }) -}) - -test("general agent denies todo tools", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const general = await load(tmp.path, (svc) => svc.get("general")) - expect(general).toBeDefined() - expect(general?.mode).toBe("subagent") - expect(general?.hidden).toBeUndefined() - expect(evalPerm(general, "todowrite")).toBe("deny") - }, - }) -}) - -test("compaction agent denies all permissions", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const compaction = await load(tmp.path, (svc) => svc.get("compaction")) - expect(compaction).toBeDefined() - expect(compaction?.hidden).toBe(true) - expect(evalPerm(compaction, "bash")).toBe("deny") - expect(evalPerm(compaction, "edit")).toBe("deny") - expect(evalPerm(compaction, "read")).toBe("deny") - }, - }) -}) - -test("custom agent from config creates new agent", async () => { - await using tmp = await tmpdir({ + }), + ), + { config: { - agent: { - my_custom_agent: { - model: "openai/gpt-4", - description: "My custom agent", - temperature: 0.5, - top_p: 0.9, + reference: { + effect: "github.com/effect/effect-smol", + effectFull: { + repository: "Effect-TS/effect", + branch: "main", + }, + localdocs: "../docs", + localdocsFull: { + path: "../local-docs", }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const custom = await load(tmp.path, (svc) => svc.get("my_custom_agent")) + }, +) + +it.instance("general agent denies todo tools", () => + Effect.gen(function* () { + const general = yield* load((svc) => svc.get("general")) + expect(general).toBeDefined() + expect(general?.mode).toBe("subagent") + expect(general?.hidden).toBeUndefined() + expect(evalPerm(general, "todowrite")).toBe("deny") + }), +) + +it.instance("compaction agent denies all permissions", () => + Effect.gen(function* () { + const compaction = yield* load((svc) => svc.get("compaction")) + expect(compaction).toBeDefined() + expect(compaction?.hidden).toBe(true) + expect(evalPerm(compaction, "bash")).toBe("deny") + expect(evalPerm(compaction, "edit")).toBe("deny") + expect(evalPerm(compaction, "read")).toBe("deny") + }), +) + +it.instance( + "custom agent from config creates new agent", + () => + Effect.gen(function* () { + const custom = yield* load((svc) => svc.get("my_custom_agent")) expect(custom).toBeDefined() expect(String(custom?.model?.providerID)).toBe("openai") expect(String(custom?.model?.modelID)).toBe("gpt-4") @@ -227,27 +198,26 @@ test("custom agent from config creates new agent", async () => { expect(custom?.topP).toBe(0.9) expect(custom?.native).toBe(false) expect(custom?.mode).toBe("all") - }, - }) -}) - -test("custom agent config overrides native agent properties", async () => { - await using tmp = await tmpdir({ + }), + { config: { agent: { - build: { - model: "anthropic/claude-3", - description: "Custom build agent", - temperature: 0.7, - color: "#FF0000", + my_custom_agent: { + model: "openai/gpt-4", + description: "My custom agent", + temperature: 0.5, + top_p: 0.9, }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) + }, +) + +it.instance( + "custom agent config overrides native agent properties", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) expect(build).toBeDefined() expect(String(build?.model?.providerID)).toBe("anthropic") expect(String(build?.model?.modelID)).toBe("claude-3") @@ -255,32 +225,52 @@ test("custom agent config overrides native agent properties", async () => { expect(build?.temperature).toBe(0.7) expect(build?.color).toBe("#FF0000") expect(build?.native).toBe(true) - }, - }) -}) - -test("agent disable removes agent from list", async () => { - await using tmp = await tmpdir({ + }), + { config: { agent: { - explore: { disable: true }, + build: { + model: "anthropic/claude-3", + description: "Custom build agent", + temperature: 0.7, + color: "#FF0000", + }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const explore = await load(tmp.path, (svc) => svc.get("explore")) + }, +) + +it.instance( + "agent disable removes agent from list", + () => + Effect.gen(function* () { + const explore = yield* load((svc) => svc.get("explore")) expect(explore).toBeUndefined() - const agents = await load(tmp.path, (svc) => svc.list()) + const agents = yield* load((svc) => svc.list()) const names = agents.map((a) => a.name) expect(names).not.toContain("explore") + }), + { + config: { + agent: { + explore: { disable: true }, + }, }, - }) -}) + }, +) -test("agent permission config merges with defaults", async () => { - await using tmp = await tmpdir({ +it.instance( + "agent permission config merges with defaults", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(build).toBeDefined() + // Specific pattern is denied + expect(Permission.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny") + // Edit still allowed + expect(evalPerm(build, "edit")).toBe("allow") + }), + { config: { agent: { build: { @@ -292,111 +282,102 @@ test("agent permission config merges with defaults", async () => { }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(build).toBeDefined() - // Specific pattern is denied - expect(Permission.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny") - // Edit still allowed - expect(evalPerm(build, "edit")).toBe("allow") - }, - }) -}) + }, +) -test("global permission config applies to all agents", async () => { - await using tmp = await tmpdir({ +it.instance( + "global permission config applies to all agents", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(build).toBeDefined() + expect(evalPerm(build, "bash")).toBe("deny") + }), + { config: { permission: { bash: "deny", }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(build).toBeDefined() - expect(evalPerm(build, "bash")).toBe("deny") - }, - }) -}) + }, +) -test("agent steps/maxSteps config sets steps property", async () => { - await using tmp = await tmpdir({ +it.instance( + "agent steps/maxSteps config sets steps property", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + const plan = yield* load((svc) => svc.get("plan")) + expect(build?.steps).toBe(50) + expect(plan?.steps).toBe(100) + }), + { config: { agent: { build: { steps: 50 }, plan: { maxSteps: 100 }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - const plan = await load(tmp.path, (svc) => svc.get("plan")) - expect(build?.steps).toBe(50) - expect(plan?.steps).toBe(100) - }, - }) -}) + }, +) -test("agent mode can be overridden", async () => { - await using tmp = await tmpdir({ +it.instance( + "agent mode can be overridden", + () => + Effect.gen(function* () { + const explore = yield* load((svc) => svc.get("explore")) + expect(explore?.mode).toBe("primary") + }), + { config: { agent: { explore: { mode: "primary" }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const explore = await load(tmp.path, (svc) => svc.get("explore")) - expect(explore?.mode).toBe("primary") - }, - }) -}) + }, +) -test("agent name can be overridden", async () => { - await using tmp = await tmpdir({ +it.instance( + "agent name can be overridden", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(build?.name).toBe("Builder") + }), + { config: { agent: { build: { name: "Builder" }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(build?.name).toBe("Builder") - }, - }) -}) + }, +) -test("agent prompt can be set from config", async () => { - await using tmp = await tmpdir({ +it.instance( + "agent prompt can be set from config", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(build?.prompt).toBe("Custom system prompt") + }), + { config: { agent: { build: { prompt: "Custom system prompt" }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(build?.prompt).toBe("Custom system prompt") - }, - }) -}) + }, +) -test("unknown agent properties are placed into options", async () => { - await using tmp = await tmpdir({ +it.instance( + "unknown agent properties are placed into options", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(build?.options.random_property).toBe("hello") + expect(build?.options.another_random).toBe(123) + }), + { config: { agent: { build: { @@ -405,19 +386,18 @@ test("unknown agent properties are placed into options", async () => { }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(build?.options.random_property).toBe("hello") - expect(build?.options.another_random).toBe(123) - }, - }) -}) + }, +) -test("agent options merge correctly", async () => { - await using tmp = await tmpdir({ +it.instance( + "agent options merge correctly", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(build?.options.custom_option).toBe(true) + expect(build?.options.another_option).toBe("value") + }), + { config: { agent: { build: { @@ -428,19 +408,21 @@ test("agent options merge correctly", async () => { }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(build?.options.custom_option).toBe(true) - expect(build?.options.another_option).toBe("value") - }, - }) -}) + }, +) -test("multiple custom agents can be defined", async () => { - await using tmp = await tmpdir({ +it.instance( + "multiple custom agents can be defined", + () => + Effect.gen(function* () { + const agentA = yield* load((svc) => svc.get("agent_a")) + const agentB = yield* load((svc) => svc.get("agent_b")) + expect(agentA?.description).toBe("Agent A") + expect(agentA?.mode).toBe("subagent") + expect(agentB?.description).toBe("Agent B") + expect(agentB?.mode).toBe("primary") + }), + { config: { agent: { agent_a: { @@ -453,22 +435,18 @@ test("multiple custom agents can be defined", async () => { }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const agentA = await load(tmp.path, (svc) => svc.get("agent_a")) - const agentB = await load(tmp.path, (svc) => svc.get("agent_b")) - expect(agentA?.description).toBe("Agent A") - expect(agentA?.mode).toBe("subagent") - expect(agentB?.description).toBe("Agent B") - expect(agentB?.mode).toBe("primary") - }, - }) -}) + }, +) -test("Agent.list keeps the default agent first and sorts the rest by name", async () => { - await using tmp = await tmpdir({ +it.instance( + "Agent.list keeps the default agent first and sorts the rest by name", + () => + Effect.gen(function* () { + const names = (yield* load((svc) => svc.list())).map((a) => a.name) + expect(names[0]).toBe("plan") + expect(names.slice(1)).toEqual(names.slice(1).toSorted((a, b) => a.localeCompare(b))) + }), + { config: { default_agent: "plan", agent: { @@ -482,53 +460,40 @@ test("Agent.list keeps the default agent first and sorts the rest by name", asyn }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const names = (await load(tmp.path, (svc) => svc.list())).map((a) => a.name) - expect(names[0]).toBe("plan") - expect(names.slice(1)).toEqual(names.slice(1).toSorted((a, b) => a.localeCompare(b))) - }, - }) -}) - -test("Agent.get returns undefined for non-existent agent", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const nonExistent = await load(tmp.path, (svc) => svc.get("does_not_exist")) - expect(nonExistent).toBeUndefined() - }, - }) -}) - -test("default permission includes doom_loop and external_directory as ask", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(evalPerm(build, "doom_loop")).toBe("ask") - expect(evalPerm(build, "external_directory")).toBe("ask") - }, - }) -}) - -test("webfetch is allowed by default", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(evalPerm(build, "webfetch")).toBe("allow") - }, - }) -}) - -test("legacy tools config converts to permissions", async () => { - await using tmp = await tmpdir({ + }, +) + +it.instance("Agent.get returns undefined for non-existent agent", () => + Effect.gen(function* () { + const nonExistent = yield* load((svc) => svc.get("does_not_exist")) + expect(nonExistent).toBeUndefined() + }), +) + +it.instance("default permission includes doom_loop and external_directory as ask", () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(evalPerm(build, "doom_loop")).toBe("ask") + expect(evalPerm(build, "external_directory")).toBe("ask") + }), +) + +it.instance("webfetch is allowed by default", () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(evalPerm(build, "webfetch")).toBe("allow") + }), +) + +it.instance( + "legacy tools config converts to permissions", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(evalPerm(build, "bash")).toBe("deny") + expect(evalPerm(build, "read")).toBe("deny") + }), + { config: { agent: { build: { @@ -539,19 +504,17 @@ test("legacy tools config converts to permissions", async () => { }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(evalPerm(build, "bash")).toBe("deny") - expect(evalPerm(build, "read")).toBe("deny") - }, - }) -}) + }, +) -test("legacy tools config maps write/edit/patch to edit permission", async () => { - await using tmp = await tmpdir({ +it.instance( + "legacy tools config maps write/edit/patch to edit permission", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(evalPerm(build, "edit")).toBe("deny") + }), + { config: { agent: { build: { @@ -561,53 +524,47 @@ test("legacy tools config maps write/edit/patch to edit permission", async () => }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(evalPerm(build, "edit")).toBe("deny") - }, - }) -}) + }, +) -test("Truncate.GLOB is allowed even when user denies external_directory globally", async () => { - const { Truncate } = await import("../../src/tool/truncate") - await using tmp = await tmpdir({ +it.instance( + "Truncate.GLOB is allowed even when user denies external_directory globally", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("allow") + expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny") + expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny") + }), + { config: { permission: { external_directory: "deny", }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) + }, +) + +it.instance("global tmp directory children are allowed for external_directory", () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(Permission.evaluate("external_directory", path.join(Global.Path.tmp, "scratch"), build!.permission).action).toBe( + "allow", + ) + expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("ask") + }), +) + +it.instance( + "Truncate.GLOB is allowed even when user denies external_directory per-agent", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("allow") expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny") expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny") - }, - }) -}) - -test("global tmp directory children are allowed for external_directory", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect( - Permission.evaluate("external_directory", path.join(Global.Path.tmp, "scratch"), build!.permission).action, - ).toBe("allow") - expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("ask") - }, - }) -}) - -test("Truncate.GLOB is allowed even when user denies external_directory per-agent", async () => { - const { Truncate } = await import("../../src/tool/truncate") - await using tmp = await tmpdir({ + }), + { config: { agent: { build: { @@ -617,21 +574,18 @@ test("Truncate.GLOB is allowed even when user denies external_directory per-agen }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("allow") - expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny") - expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny") - }, - }) -}) + }, +) -test("explicit Truncate.GLOB deny is respected", async () => { - const { Truncate } = await import("../../src/tool/truncate") - await using tmp = await tmpdir({ +it.instance( + "explicit Truncate.GLOB deny is respected", + () => + Effect.gen(function* () { + const build = yield* load((svc) => svc.get("build")) + expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("deny") + expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny") + }), + { config: { permission: { external_directory: { @@ -640,81 +594,72 @@ test("explicit Truncate.GLOB deny is respected", async () => { }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("deny") - expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny") - }, - }) -}) - -test("skill directories are allowed for external_directory", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - const skillDir = path.join(dir, ".opencode", "skill", "perm-skill") - await Bun.write( - path.join(skillDir, "SKILL.md"), - `--- + }, +) + +it.instance( + "skill directories are allowed for external_directory", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const skillDir = path.join(test.directory, ".opencode", "skill", "perm-skill") + yield* Effect.promise(() => + Bun.write( + path.join(skillDir, "SKILL.md"), + `--- name: perm-skill description: Permission skill. --- # Permission Skill `, + ), ) - }, - }) - - const home = process.env.OPENCODE_TEST_HOME - process.env.OPENCODE_TEST_HOME = tmp.path - - try { - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const build = await load(tmp.path, (svc) => svc.get("build")) - const skillDir = path.join(tmp.path, ".opencode", "skill", "perm-skill") - const target = path.join(skillDir, "reference", "notes.md") - expect(Permission.evaluate("external_directory", target, build!.permission).action).toBe("allow") - }, - }) - } finally { - process.env.OPENCODE_TEST_HOME = home - } -}) -test("defaultAgent returns build when no default_agent config", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const agent = await load(tmp.path, (svc) => svc.defaultAgent()) - expect(agent).toBe("build") - }, - }) -}) + const home = process.env.OPENCODE_TEST_HOME + process.env.OPENCODE_TEST_HOME = test.directory + yield* Effect.addFinalizer(() => + Effect.sync(() => { + process.env.OPENCODE_TEST_HOME = home + }), + ) -test("defaultAgent respects default_agent config set to plan", async () => { - await using tmp = await tmpdir({ + const build = yield* load((svc) => svc.get("build")) + const target = path.join(skillDir, "reference", "notes.md") + expect(Permission.evaluate("external_directory", target, build!.permission).action).toBe("allow") + }), + { git: true }, +) + +it.instance("defaultAgent returns build when no default_agent config", () => + Effect.gen(function* () { + const agent = yield* load((svc) => svc.defaultAgent()) + expect(agent).toBe("build") + }), +) + +it.instance( + "defaultAgent respects default_agent config set to plan", + () => + Effect.gen(function* () { + const agent = yield* load((svc) => svc.defaultAgent()) + expect(agent).toBe("plan") + }), + { config: { default_agent: "plan", }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const agent = await load(tmp.path, (svc) => svc.defaultAgent()) - expect(agent).toBe("plan") - }, - }) -}) + }, +) -test("defaultAgent respects default_agent config set to custom agent with mode all", async () => { - await using tmp = await tmpdir({ +it.instance( + "defaultAgent respects default_agent config set to custom agent with mode all", + () => + Effect.gen(function* () { + const agent = yield* load((svc) => svc.defaultAgent()) + expect(agent).toBe("my_custom") + }), + { config: { default_agent: "my_custom", agent: { @@ -723,92 +668,65 @@ test("defaultAgent respects default_agent config set to custom agent with mode a }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const agent = await load(tmp.path, (svc) => svc.defaultAgent()) - expect(agent).toBe("my_custom") - }, - }) -}) + }, +) -test("defaultAgent throws when default_agent points to subagent", async () => { - await using tmp = await tmpdir({ +it.instance( + "defaultAgent throws when default_agent points to subagent", + () => expectDefaultAgentError('default agent "explore" is a subagent'), + { config: { default_agent: "explore", }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow('default agent "explore" is a subagent') - }, - }) -}) + }, +) -test("defaultAgent throws when default_agent points to hidden agent", async () => { - await using tmp = await tmpdir({ +it.instance( + "defaultAgent throws when default_agent points to hidden agent", + () => expectDefaultAgentError('default agent "compaction" is hidden'), + { config: { default_agent: "compaction", }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow('default agent "compaction" is hidden') - }, - }) -}) + }, +) -test("defaultAgent throws when default_agent points to non-existent agent", async () => { - await using tmp = await tmpdir({ +it.instance( + "defaultAgent throws when default_agent points to non-existent agent", + () => expectDefaultAgentError('default agent "does_not_exist" not found'), + { config: { default_agent: "does_not_exist", }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow( - 'default agent "does_not_exist" not found', - ) - }, - }) -}) + }, +) -test("defaultAgent returns plan when build is disabled and default_agent not set", async () => { - await using tmp = await tmpdir({ +it.instance( + "defaultAgent returns plan when build is disabled and default_agent not set", + () => + Effect.gen(function* () { + const agent = yield* load((svc) => svc.defaultAgent()) + // build is disabled, so it should return plan (next primary agent) + expect(agent).toBe("plan") + }), + { config: { agent: { build: { disable: true }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const agent = await load(tmp.path, (svc) => svc.defaultAgent()) - // build is disabled, so it should return plan (next primary agent) - expect(agent).toBe("plan") - }, - }) -}) + }, +) -test("defaultAgent throws when all primary agents are disabled", async () => { - await using tmp = await tmpdir({ +it.instance( + "defaultAgent throws when all primary agents are disabled", + () => expectDefaultAgentError("no primary visible agent found"), + { config: { agent: { build: { disable: true }, plan: { disable: true }, }, }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - // build and plan are disabled, no primary-capable agents remain - await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow("no primary visible agent found") - }, - }) -}) + }, +) From b668af29dd7c2117ed4e5868a6d2f1d73237fbcb Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:50:07 -0400 Subject: [PATCH 120/378] test(git): migrate git tests to Effect runner (#27121) --- packages/opencode/test/git/git.test.ts | 223 +++++++++++++------------ 1 file changed, 113 insertions(+), 110 deletions(-) diff --git a/packages/opencode/test/git/git.test.ts b/packages/opencode/test/git/git.test.ts index 1e56865d7..e80b8fa90 100644 --- a/packages/opencode/test/git/git.test.ts +++ b/packages/opencode/test/git/git.test.ts @@ -1,71 +1,70 @@ import { $ } from "bun" -import { describe, expect, test } from "bun:test" +import { describe, expect } from "bun:test" import fs from "fs/promises" import path from "path" -import { ManagedRuntime } from "effect" +import { Effect } from "effect" import { Git } from "../../src/git" import { tmpdir } from "../fixture/fixture" +import { testEffect } from "../lib/effect" const weird = process.platform === "win32" ? "space file.txt" : "tab\tfile.txt" +const it = testEffect(Git.defaultLayer) -async function withGit(body: (rt: ManagedRuntime.ManagedRuntime) => Promise) { - const rt = ManagedRuntime.make(Git.defaultLayer) - try { - return await body(rt) - } finally { - await rt.dispose() - } -} +const scopedTmpdir = (options?: Parameters[0]) => + Effect.acquireRelease( + Effect.promise(() => tmpdir(options)), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ) describe("Git", () => { - test("branch() returns current branch name", async () => { - await using tmp = await tmpdir({ git: true }) - - await withGit(async (rt) => { - const branch = await rt.runPromise(Git.Service.use((git) => git.branch(tmp.path))) + it.live("branch() returns current branch name", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir({ git: true }) + const git = yield* Git.Service + const branch = yield* git.branch(tmp.path) expect(branch).toBeDefined() expect(typeof branch).toBe("string") - }) - }) - - test("branch() returns undefined for non-git directories", async () => { - await using tmp = await tmpdir() - - await withGit(async (rt) => { - const branch = await rt.runPromise(Git.Service.use((git) => git.branch(tmp.path))) + }), + ) + + it.live("branch() returns undefined for non-git directories", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir() + const git = yield* Git.Service + const branch = yield* git.branch(tmp.path) expect(branch).toBeUndefined() - }) - }) - - test("branch() returns undefined for detached HEAD", async () => { - await using tmp = await tmpdir({ git: true }) - const hash = (await $`git rev-parse HEAD`.cwd(tmp.path).quiet().text()).trim() - await $`git checkout --detach ${hash}`.cwd(tmp.path).quiet() - - await withGit(async (rt) => { - const branch = await rt.runPromise(Git.Service.use((git) => git.branch(tmp.path))) + }), + ) + + it.live("branch() returns undefined for detached HEAD", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir({ git: true }) + const hash = (yield* Effect.promise(() => $`git rev-parse HEAD`.cwd(tmp.path).quiet().text())).trim() + yield* Effect.promise(() => $`git checkout --detach ${hash}`.cwd(tmp.path).quiet()) + const git = yield* Git.Service + const branch = yield* git.branch(tmp.path) expect(branch).toBeUndefined() - }) - }) - - test("defaultBranch() uses init.defaultBranch when available", async () => { - await using tmp = await tmpdir({ git: true }) - await $`git branch -M trunk`.cwd(tmp.path).quiet() - await $`git config init.defaultBranch trunk`.cwd(tmp.path).quiet() - - await withGit(async (rt) => { - const branch = await rt.runPromise(Git.Service.use((git) => git.defaultBranch(tmp.path))) + }), + ) + + it.live("defaultBranch() uses init.defaultBranch when available", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir({ git: true }) + yield* Effect.promise(() => $`git branch -M trunk`.cwd(tmp.path).quiet()) + yield* Effect.promise(() => $`git config init.defaultBranch trunk`.cwd(tmp.path).quiet()) + const git = yield* Git.Service + const branch = yield* git.defaultBranch(tmp.path) expect(branch?.name).toBe("trunk") expect(branch?.ref).toBe("trunk") - }) - }) - - test("status() handles special filenames", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, weird), "hello\n", "utf-8") - - await withGit(async (rt) => { - const status = await rt.runPromise(Git.Service.use((git) => git.status(tmp.path))) + }), + ) + + it.live("status() handles special filenames", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir({ git: true }) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, weird), "hello\n", "utf-8")) + const git = yield* Git.Service + const status = yield* git.status(tmp.path) expect(status).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -74,23 +73,24 @@ describe("Git", () => { }), ]), ) - }) - }) - - test("diff(), stats(), and mergeBase() parse tracked changes", async () => { - await using tmp = await tmpdir({ git: true }) - await $`git branch -M main`.cwd(tmp.path).quiet() - await fs.writeFile(path.join(tmp.path, weird), "before\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet() - await $`git checkout -b feature/test`.cwd(tmp.path).quiet() - await fs.writeFile(path.join(tmp.path, weird), "after\n", "utf-8") - - await withGit(async (rt) => { - const [base, diff, stats] = await Promise.all([ - rt.runPromise(Git.Service.use((git) => git.mergeBase(tmp.path, "main"))), - rt.runPromise(Git.Service.use((git) => git.diff(tmp.path, "HEAD"))), - rt.runPromise(Git.Service.use((git) => git.stats(tmp.path, "HEAD"))), + }), + ) + + it.live("diff(), stats(), and mergeBase() parse tracked changes", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir({ git: true }) + yield* Effect.promise(() => $`git branch -M main`.cwd(tmp.path).quiet()) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, weird), "before\n", "utf-8")) + yield* Effect.promise(() => $`git add .`.cwd(tmp.path).quiet()) + yield* Effect.promise(() => $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet()) + yield* Effect.promise(() => $`git checkout -b feature/test`.cwd(tmp.path).quiet()) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, weird), "after\n", "utf-8")) + + const git = yield* Git.Service + const [base, diff, stats] = yield* Effect.all([ + git.mergeBase(tmp.path, "main"), + git.diff(tmp.path, "HEAD"), + git.stats(tmp.path, "HEAD"), ]) expect(base).toBeTruthy() @@ -111,23 +111,24 @@ describe("Git", () => { }), ]), ) - }) - }) - - test("patch() returns capped native patch output", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, weird), "before\n", "utf-8") - await fs.writeFile(path.join(tmp.path, "other.txt"), "old\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet() - await fs.writeFile(path.join(tmp.path, weird), "after\n", "utf-8") - await fs.writeFile(path.join(tmp.path, "other.txt"), "new\n", "utf-8") - - await withGit(async (rt) => { - const [patch, all, capped] = await Promise.all([ - rt.runPromise(Git.Service.use((git) => git.patch(tmp.path, "HEAD", weird, { context: 2_147_483_647 }))), - rt.runPromise(Git.Service.use((git) => git.patchAll(tmp.path, "HEAD", { context: 2_147_483_647 }))), - rt.runPromise(Git.Service.use((git) => git.patch(tmp.path, "HEAD", weird, { maxOutputBytes: 1 }))), + }), + ) + + it.live("patch() returns capped native patch output", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir({ git: true }) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, weird), "before\n", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "other.txt"), "old\n", "utf-8")) + yield* Effect.promise(() => $`git add .`.cwd(tmp.path).quiet()) + yield* Effect.promise(() => $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet()) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, weird), "after\n", "utf-8")) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "other.txt"), "new\n", "utf-8")) + + const git = yield* Git.Service + const [patch, all, capped] = yield* Effect.all([ + git.patch(tmp.path, "HEAD", weird, { context: 2_147_483_647 }), + git.patchAll(tmp.path, "HEAD", { context: 2_147_483_647 }), + git.patch(tmp.path, "HEAD", weird, { maxOutputBytes: 1 }), ]) expect(patch.truncated).toBe(false) @@ -140,17 +141,18 @@ describe("Git", () => { expect(all.text).toContain("+new") expect(capped.truncated).toBe(true) expect(capped.text).toBe("") - }) - }) - - test("patchUntracked() and statUntracked() handle added files", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, weird), "one\ntwo\n", "utf-8") - - await withGit(async (rt) => { - const [patch, stat] = await Promise.all([ - rt.runPromise(Git.Service.use((git) => git.patchUntracked(tmp.path, weird, { context: 2_147_483_647 }))), - rt.runPromise(Git.Service.use((git) => git.statUntracked(tmp.path, weird))), + }), + ) + + it.live("patchUntracked() and statUntracked() handle added files", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir({ git: true }) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, weird), "one\ntwo\n", "utf-8")) + + const git = yield* Git.Service + const [patch, stat] = yield* Effect.all([ + git.patchUntracked(tmp.path, weird, { context: 2_147_483_647 }), + git.statUntracked(tmp.path, weird), ]) expect(patch.truncated).toBe(false) @@ -158,18 +160,19 @@ describe("Git", () => { expect(patch.text).toContain("+one") expect(patch.text).toContain("+two") expect(stat).toEqual(expect.objectContaining({ file: weird, additions: 2, deletions: 0 })) - }) - }) - - test("show() returns empty text for binary blobs", async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "bin.dat"), new Uint8Array([0, 1, 2, 3])) - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit --no-gpg-sign -m "add binary"`.cwd(tmp.path).quiet() - - await withGit(async (rt) => { - const text = await rt.runPromise(Git.Service.use((git) => git.show(tmp.path, "HEAD", "bin.dat"))) + }), + ) + + it.live("show() returns empty text for binary blobs", () => + Effect.gen(function* () { + const tmp = yield* scopedTmpdir({ git: true }) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "bin.dat"), new Uint8Array([0, 1, 2, 3]))) + yield* Effect.promise(() => $`git add .`.cwd(tmp.path).quiet()) + yield* Effect.promise(() => $`git commit --no-gpg-sign -m "add binary"`.cwd(tmp.path).quiet()) + + const git = yield* Git.Service + const text = yield* git.show(tmp.path, "HEAD", "bin.dat") expect(text).toBe("") - }) - }) + }), + ) }) From 8f1ded9e0812e5bc1288a850d58290d76715105c Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:50:26 -0400 Subject: [PATCH 121/378] test(file): migrate ripgrep tests to Effect runner (#27120) --- packages/opencode/test/file/ripgrep.test.ts | 414 ++++++++++---------- 1 file changed, 210 insertions(+), 204 deletions(-) diff --git a/packages/opencode/test/file/ripgrep.test.ts b/packages/opencode/test/file/ripgrep.test.ts index a76c7ebe2..d71ce205e 100644 --- a/packages/opencode/test/file/ripgrep.test.ts +++ b/packages/opencode/test/file/ripgrep.test.ts @@ -1,214 +1,220 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect } from "bun:test" import { Effect } from "effect" import * as Stream from "effect/Stream" import fs from "fs/promises" +import os from "os" import path from "path" -import { tmpdir } from "../fixture/fixture" import { Ripgrep } from "../../src/file/ripgrep" - -const run = (effect: Effect.Effect) => - effect.pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromise) +import { testEffect } from "../lib/effect" + +const it = testEffect(Ripgrep.defaultLayer) + +const tmpdir = (init?: (dir: string) => Effect.Effect) => + Effect.acquireRelease( + Effect.promise(async () => fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(), "opencode-test-")))), + (dir) => + Effect.promise(() => + fs.rm(dir, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }), + ).pipe(Effect.ignore), + ).pipe(Effect.tap((dir) => init?.(dir) ?? Effect.void)) + +const write = (file: string, data: string) => Effect.promise(() => Bun.write(file, data)) +const mkdir = (dir: string) => Effect.promise(() => fs.mkdir(dir, { recursive: true })) +const collectFiles = (input: Ripgrep.FilesInput) => + Ripgrep.Service.use((rg) => + rg.files(input).pipe( + Stream.runCollect, + Effect.map((c) => [...c]), + ), + ) + +const withRipgrepConfig = (value: string, effect: Effect.Effect) => + Effect.acquireUseRelease( + Effect.sync(() => { + const prev = process.env["RIPGREP_CONFIG_PATH"] + process.env["RIPGREP_CONFIG_PATH"] = value + return prev + }), + () => effect, + (prev) => + Effect.sync(() => { + if (prev === undefined) delete process.env["RIPGREP_CONFIG_PATH"] + else process.env["RIPGREP_CONFIG_PATH"] = prev + }), + ) describe("file.ripgrep", () => { - test("defaults to include hidden", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "visible.txt"), "hello") - await fs.mkdir(path.join(dir, ".opencode"), { recursive: true }) - await Bun.write(path.join(dir, ".opencode", "thing.json"), "{}") - }, - }) - - const files = await run( - Ripgrep.Service.use((rg) => - rg.files({ cwd: tmp.path }).pipe( - Stream.runCollect, - Effect.map((c) => [...c]), - ), - ), - ) - expect(files.includes("visible.txt")).toBe(true) - expect(files.includes(path.join(".opencode", "thing.json"))).toBe(true) - }) - - test("hidden false excludes hidden", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "visible.txt"), "hello") - await fs.mkdir(path.join(dir, ".opencode"), { recursive: true }) - await Bun.write(path.join(dir, ".opencode", "thing.json"), "{}") - }, - }) - - const files = await run( - Ripgrep.Service.use((rg) => - rg.files({ cwd: tmp.path, hidden: false }).pipe( - Stream.runCollect, - Effect.map((c) => [...c]), - ), - ), - ) - expect(files.includes("visible.txt")).toBe(true) - expect(files.includes(path.join(".opencode", "thing.json"))).toBe(false) - }) - - test("search returns empty when nothing matches", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "match.ts"), "const value = 'other'\n") - }, - }) - - const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle" }))) - expect(result.partial).toBe(false) - expect(result.items).toEqual([]) - }) - - test("search returns match metadata with normalized path", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await fs.mkdir(path.join(dir, "src"), { recursive: true }) - await Bun.write(path.join(dir, "src", "match.ts"), "const needle = 1\n") - }, - }) - - const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle" }))) - expect(result.partial).toBe(false) - expect(result.items).toHaveLength(1) - expect(result.items[0]?.path.text).toBe(path.join("src", "match.ts")) - expect(result.items[0]?.line_number).toBe(1) - expect(result.items[0]?.lines.text).toContain("needle") - }) - - test("search returns matched rows with glob filter", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "match.ts"), "const value = 'needle'\n") - await Bun.write(path.join(dir, "skip.txt"), "const value = 'other'\n") - }, - }) - - const result = await run( - Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle", glob: ["*.ts"] })), - ) - expect(result.partial).toBe(false) - expect(result.items).toHaveLength(1) - expect(result.items[0]?.path.text).toContain("match.ts") - expect(result.items[0]?.lines.text).toContain("needle") - }) - - test("search supports explicit file targets", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "match.ts"), "const value = 'needle'\n") - await Bun.write(path.join(dir, "skip.ts"), "const value = 'needle'\n") - }, - }) - - const file = path.join(tmp.path, "match.ts") - const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle", file: [file] }))) - expect(result.partial).toBe(false) - expect(result.items).toHaveLength(1) - expect(result.items[0]?.path.text).toBe(file) - }) - - test("files returns empty when glob matches no files", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await fs.mkdir(path.join(dir, "packages", "console"), { recursive: true }) - await Bun.write(path.join(dir, "packages", "console", "package.json"), "{}") - }, - }) - - const files = await run( - Ripgrep.Service.use((rg) => - rg.files({ cwd: tmp.path, glob: ["packages/*"] }).pipe( - Stream.runCollect, - Effect.map((c) => [...c]), - ), - ), - ) - expect(files).toEqual([]) - }) - - test("files returns stream of filenames", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "a.txt"), "hello") - await Bun.write(path.join(dir, "b.txt"), "world") - }, - }) - - const files = await run( - Ripgrep.Service.use((rg) => - rg.files({ cwd: tmp.path }).pipe( - Stream.runCollect, - Effect.map((c) => [...c].sort()), - ), - ), - ) - expect(files).toEqual(["a.txt", "b.txt"]) - }) - - test("files respects glob filter", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "keep.ts"), "yes") - await Bun.write(path.join(dir, "skip.txt"), "no") - }, - }) - - const files = await run( - Ripgrep.Service.use((rg) => - rg.files({ cwd: tmp.path, glob: ["*.ts"] }).pipe( - Stream.runCollect, - Effect.map((c) => [...c]), - ), - ), - ) - expect(files).toEqual(["keep.ts"]) - }) - - test("files dies on nonexistent directory", async () => { - const exit = await Ripgrep.Service.use((rg) => - rg.files({ cwd: "/tmp/nonexistent-dir-12345" }).pipe(Stream.runCollect), - ).pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromiseExit) - expect(exit._tag).toBe("Failure") - }) - - test("ignores RIPGREP_CONFIG_PATH in direct mode", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "match.ts"), "const needle = 1\n") - }, - }) - - const prev = process.env["RIPGREP_CONFIG_PATH"] - process.env["RIPGREP_CONFIG_PATH"] = path.join(tmp.path, "missing-ripgreprc") - try { - const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle" }))) + it.live("defaults to include hidden", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => + Effect.gen(function* () { + yield* write(path.join(dir, "visible.txt"), "hello") + yield* mkdir(path.join(dir, ".opencode")) + yield* write(path.join(dir, ".opencode", "thing.json"), "{}") + }), + ) + + const files = yield* collectFiles({ cwd: dir }) + expect(files.includes("visible.txt")).toBe(true) + expect(files.includes(path.join(".opencode", "thing.json"))).toBe(true) + }), + ) + + it.live("hidden false excludes hidden", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => + Effect.gen(function* () { + yield* write(path.join(dir, "visible.txt"), "hello") + yield* mkdir(path.join(dir, ".opencode")) + yield* write(path.join(dir, ".opencode", "thing.json"), "{}") + }), + ) + + const files = yield* collectFiles({ cwd: dir, hidden: false }) + expect(files.includes("visible.txt")).toBe(true) + expect(files.includes(path.join(".opencode", "thing.json"))).toBe(false) + }), + ) + + it.live("search returns empty when nothing matches", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => write(path.join(dir, "match.ts"), "const value = 'other'\n")) + + const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" })) + expect(result.partial).toBe(false) + expect(result.items).toEqual([]) + }), + ) + + it.live("search returns match metadata with normalized path", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => + Effect.gen(function* () { + yield* mkdir(path.join(dir, "src")) + yield* write(path.join(dir, "src", "match.ts"), "const needle = 1\n") + }), + ) + + const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" })) + expect(result.partial).toBe(false) + expect(result.items).toHaveLength(1) + expect(result.items[0]?.path.text).toBe(path.join("src", "match.ts")) + expect(result.items[0]?.line_number).toBe(1) + expect(result.items[0]?.lines.text).toContain("needle") + }), + ) + + it.live("search returns matched rows with glob filter", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => + Effect.gen(function* () { + yield* write(path.join(dir, "match.ts"), "const value = 'needle'\n") + yield* write(path.join(dir, "skip.txt"), "const value = 'other'\n") + }), + ) + + const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle", glob: ["*.ts"] })) + expect(result.partial).toBe(false) + expect(result.items).toHaveLength(1) + expect(result.items[0]?.path.text).toContain("match.ts") + expect(result.items[0]?.lines.text).toContain("needle") + }), + ) + + it.live("search supports explicit file targets", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => + Effect.gen(function* () { + yield* write(path.join(dir, "match.ts"), "const value = 'needle'\n") + yield* write(path.join(dir, "skip.ts"), "const value = 'needle'\n") + }), + ) + + const file = path.join(dir, "match.ts") + const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle", file: [file] })) + expect(result.partial).toBe(false) expect(result.items).toHaveLength(1) - } finally { - if (prev === undefined) delete process.env["RIPGREP_CONFIG_PATH"] - else process.env["RIPGREP_CONFIG_PATH"] = prev - } - }) - - test("ignores RIPGREP_CONFIG_PATH in worker mode", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "match.ts"), "const needle = 1\n") - }, - }) - - const prev = process.env["RIPGREP_CONFIG_PATH"] - process.env["RIPGREP_CONFIG_PATH"] = path.join(tmp.path, "missing-ripgreprc") - try { - const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle" }))) + expect(result.items[0]?.path.text).toBe(file) + }), + ) + + it.live("files returns empty when glob matches no files", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => + Effect.gen(function* () { + yield* mkdir(path.join(dir, "packages", "console")) + yield* write(path.join(dir, "packages", "console", "package.json"), "{}") + }), + ) + + const files = yield* collectFiles({ cwd: dir, glob: ["packages/*"] }) + expect(files).toEqual([]) + }), + ) + + it.live("files returns stream of filenames", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => + Effect.gen(function* () { + yield* write(path.join(dir, "a.txt"), "hello") + yield* write(path.join(dir, "b.txt"), "world") + }), + ) + + const files = yield* collectFiles({ cwd: dir }).pipe(Effect.map((files) => files.sort())) + expect(files).toEqual(["a.txt", "b.txt"]) + }), + ) + + it.live("files respects glob filter", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => + Effect.gen(function* () { + yield* write(path.join(dir, "keep.ts"), "yes") + yield* write(path.join(dir, "skip.txt"), "no") + }), + ) + + const files = yield* collectFiles({ cwd: dir, glob: ["*.ts"] }) + expect(files).toEqual(["keep.ts"]) + }), + ) + + it.live("files dies on nonexistent directory", () => + Effect.gen(function* () { + const exit = yield* Ripgrep.Service.use((rg) => + rg.files({ cwd: "/tmp/nonexistent-dir-12345" }).pipe(Stream.runCollect), + ).pipe(Effect.exit) + expect(exit._tag).toBe("Failure") + }), + ) + + it.live("ignores RIPGREP_CONFIG_PATH in direct mode", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => write(path.join(dir, "match.ts"), "const needle = 1\n")) + + const result = yield* withRipgrepConfig( + path.join(dir, "missing-ripgreprc"), + Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" })), + ) + expect(result.items).toHaveLength(1) + }), + ) + + it.live("ignores RIPGREP_CONFIG_PATH in worker mode", () => + Effect.gen(function* () { + const dir = yield* tmpdir((dir) => write(path.join(dir, "match.ts"), "const needle = 1\n")) + + const result = yield* withRipgrepConfig( + path.join(dir, "missing-ripgreprc"), + Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" })), + ) expect(result.items).toHaveLength(1) - } finally { - if (prev === undefined) delete process.env["RIPGREP_CONFIG_PATH"] - else process.env["RIPGREP_CONFIG_PATH"] = prev - } - }) + }), + ) }) From 3c7569d852a07e245357eaaf73c4399765c5b68e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:50:56 -0400 Subject: [PATCH 122/378] test(tool): migrate external directory tests to Effect runner (#27122) --- .../test/tool/external-directory.test.ts | 219 ++++++++---------- 1 file changed, 101 insertions(+), 118 deletions(-) diff --git a/packages/opencode/test/tool/external-directory.test.ts b/packages/opencode/test/tool/external-directory.test.ts index 0560ea030..7585ff98f 100644 --- a/packages/opencode/test/tool/external-directory.test.ts +++ b/packages/opencode/test/tool/external-directory.test.ts @@ -1,14 +1,16 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect } from "bun:test" import path from "path" import { Effect } from "effect" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import type { Tool } from "@/tool/tool" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" -import { assertExternalDirectory } from "../../src/tool/external-directory" +import { assertExternalDirectoryEffect } from "../../src/tool/external-directory" import { Filesystem } from "@/util/filesystem" -import { tmpdir } from "../fixture/fixture" +import { provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" import type { Permission } from "../../src/permission" import { SessionID, MessageID } from "../../src/session/schema" +import { testEffect } from "../lib/effect" + +const it = testEffect(CrossSpawnSpawner.defaultLayer) const baseCtx: Omit = { sessionID: SessionID.make("ses_test"), @@ -36,135 +38,116 @@ function makeCtx() { } describe("tool.assertExternalDirectory", () => { - test("no-ops for empty target", async () => { - const { requests, ctx } = makeCtx() - - await WithInstance.provide({ - directory: "/tmp", - fn: async () => { - await assertExternalDirectory(ctx) - }, - }) - - expect(requests.length).toBe(0) - }) - - test("no-ops for paths inside Instance.directory", async () => { - const { requests, ctx } = makeCtx() - - await WithInstance.provide({ - directory: "/tmp/project", - fn: async () => { - await assertExternalDirectory(ctx, path.join("/tmp/project", "file.txt")) - }, - }) - - expect(requests.length).toBe(0) - }) - - test("asks with a single canonical glob", async () => { - const { requests, ctx } = makeCtx() - - const directory = "/tmp/project" - const target = "/tmp/outside/file.txt" - const expected = glob(path.join(path.dirname(target), "*")) - - await WithInstance.provide({ - directory, - fn: async () => { - await assertExternalDirectory(ctx, target) - }, - }) - - const req = requests.find((r) => r.permission === "external_directory") - expect(req).toBeDefined() - expect(req!.patterns).toEqual([expected]) - expect(req!.always).toEqual([expected]) - }) - - test("uses target directory when kind=directory", async () => { - const { requests, ctx } = makeCtx() - - const directory = "/tmp/project" - const target = "/tmp/outside" - const expected = glob(path.join(target, "*")) - - await WithInstance.provide({ - directory, - fn: async () => { - await assertExternalDirectory(ctx, target, { kind: "directory" }) - }, - }) - - const req = requests.find((r) => r.permission === "external_directory") - expect(req).toBeDefined() - expect(req!.patterns).toEqual([expected]) - expect(req!.always).toEqual([expected]) - }) - - test("skips prompting when bypass=true", async () => { - const { requests, ctx } = makeCtx() - - await WithInstance.provide({ - directory: "/tmp/project", - fn: async () => { - await assertExternalDirectory(ctx, "/tmp/outside/file.txt", { bypass: true }) - }, - }) - - expect(requests.length).toBe(0) - }) + it.live("no-ops for empty target", () => + Effect.gen(function* () { + const { requests, ctx } = makeCtx() - if (process.platform === "win32") { - test("normalizes Windows path variants to one glob", async () => { + yield* assertExternalDirectoryEffect(ctx) + + expect(requests.length).toBe(0) + }), + ) + + it.live("no-ops for paths inside Instance.directory", () => + provideInstance("/tmp/project")( + Effect.gen(function* () { + const { requests, ctx } = makeCtx() + + yield* assertExternalDirectoryEffect(ctx, path.join("/tmp/project", "file.txt")) + + expect(requests.length).toBe(0) + }), + ), + ) + + it.live("asks with a single canonical glob", () => + Effect.gen(function* () { const { requests, ctx } = makeCtx() - await using outerTmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "outside.txt"), "x") - }, - }) - await using tmp = await tmpdir({ git: true }) - - const target = path.join(outerTmp.path, "outside.txt") - const alt = target - .replace(/^[A-Za-z]:/, "") - .replaceAll("\\", "/") - .toLowerCase() - - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await assertExternalDirectory(ctx, alt) - }, - }) + const directory = "/tmp/project" + const target = "/tmp/outside/file.txt" + const expected = glob(path.join(path.dirname(target), "*")) + + yield* provideInstance(directory)(assertExternalDirectoryEffect(ctx, target)) const req = requests.find((r) => r.permission === "external_directory") - const expected = glob(path.join(outerTmp.path, "*")) expect(req).toBeDefined() expect(req!.patterns).toEqual([expected]) expect(req!.always).toEqual([expected]) - }) + }), + ) - test("uses drive root glob for root files", async () => { + it.live("uses target directory when kind=directory", () => + Effect.gen(function* () { const { requests, ctx } = makeCtx() - await using tmp = await tmpdir({ git: true }) - const root = path.parse(tmp.path).root - const target = path.join(root, "boot.ini") + const directory = "/tmp/project" + const target = "/tmp/outside" + const expected = glob(path.join(target, "*")) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - await assertExternalDirectory(ctx, target) - }, - }) + yield* provideInstance(directory)(assertExternalDirectoryEffect(ctx, target, { kind: "directory" })) const req = requests.find((r) => r.permission === "external_directory") - const expected = path.join(root, "*") expect(req).toBeDefined() expect(req!.patterns).toEqual([expected]) expect(req!.always).toEqual([expected]) - }) + }), + ) + + it.live("skips prompting when bypass=true", () => + provideInstance("/tmp/project")( + Effect.gen(function* () { + const { requests, ctx } = makeCtx() + + yield* assertExternalDirectoryEffect(ctx, "/tmp/outside/file.txt", { bypass: true }) + + expect(requests.length).toBe(0) + }), + ), + ) + + if (process.platform === "win32") { + it.instance("normalizes Windows path variants to one glob", () => + Effect.gen(function* () { + const { requests, ctx } = makeCtx() + + const outerTmp = yield* tmpdirScoped() + yield* Effect.promise(() => Bun.write(path.join(outerTmp, "outside.txt"), "x")) + + const target = path.join(outerTmp, "outside.txt") + const alt = target + .replace(/^[A-Za-z]:/, "") + .replaceAll("\\", "/") + .toLowerCase() + + yield* assertExternalDirectoryEffect(ctx, alt) + + const req = requests.find((r) => r.permission === "external_directory") + const expected = glob(path.join(outerTmp, "*")) + expect(req).toBeDefined() + expect(req!.patterns).toEqual([expected]) + expect(req!.always).toEqual([expected]) + }), + { git: true }, + ) + + it.instance("uses drive root glob for root files", () => + Effect.gen(function* () { + const { requests, ctx } = makeCtx() + + const tmp = yield* TestInstance + const root = path.parse(tmp.directory).root + const target = path.join(root, "boot.ini") + + yield* assertExternalDirectoryEffect(ctx, target) + + const req = requests.find((r) => r.permission === "external_directory") + const expected = path.join(root, "*") + expect(req).toBeDefined() + expect(req!.patterns).toEqual([expected]) + expect(req!.always).toEqual([expected]) + }), + { git: true }, + ) } }) From 549b146ea6529cc29dd556644543fcb48acf6d45 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:51:18 -0400 Subject: [PATCH 123/378] Stabilize session event tests (#27117) --- .../opencode/test/session/session.test.ts | 304 +++++++++--------- 1 file changed, 146 insertions(+), 158 deletions(-) diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index bb69e459b..ada55d134 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -1,186 +1,174 @@ -import { describe, expect, test } from "bun:test" -import path from "path" +import { describe, expect } from "bun:test" +import { Deferred, Effect, Exit, Layer } from "effect" import { Session as SessionNs } from "@/session/session" -import { Bus } from "../../src/bus" +import { GlobalBus, type GlobalEvent } from "../../src/bus/global" import * as Log from "@opencode-ai/core/util/log" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" +import { Flag } from "@opencode-ai/core/flag/flag" import { MessageV2 } from "../../src/session/message-v2" import { MessageID, PartID, type SessionID } from "../../src/session/schema" -import { AppRuntime } from "../../src/effect/app-runtime" -import { tmpdir } from "../fixture/fixture" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { provideInstance, tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" -const projectRoot = path.join(__dirname, "../..") void Log.init({ print: false }) -function create(input?: SessionNs.CreateInput) { - return AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create(input))) -} - -function get(id: SessionID) { - return AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.get(id))) -} +const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, CrossSpawnSpawner.defaultLayer)) -function remove(id: SessionID) { - return AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.remove(id))) -} +const awaitDeferred = (deferred: Deferred.Deferred, message: string) => + Effect.race( + Deferred.await(deferred), + Effect.sleep("2 seconds").pipe(Effect.flatMap(() => Effect.fail(new Error(message)))), + ) -function updateMessage(msg: T) { - return AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.updateMessage(msg))) -} +const remove = (id: SessionID) => SessionNs.Service.use((svc) => svc.remove(id)) -function updatePart(part: T) { - return AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.updatePart(part))) +const subscribeGlobal = (type: string, callback: (event: NonNullable) => void) => { + const listener = (event: GlobalEvent) => { + if (event.payload?.type === type) callback(event.payload) + } + GlobalBus.on("event", listener) + return () => GlobalBus.off("event", listener) } describe("session.created event", () => { - test("should emit session.created event when session is created", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - let eventReceived = false - let receivedInfo: SessionNs.Info | undefined - - const unsub = Bus.subscribe(SessionNs.Event.Created, (event) => { - eventReceived = true - receivedInfo = event.properties.info as SessionNs.Info - }) + it.instance("should emit session.created event when session is created", () => + Effect.gen(function* () { + const session = yield* SessionNs.Service + const received = yield* Deferred.make() - const info = await create({}) - await new Promise((resolve) => setTimeout(resolve, 100)) - unsub() - - expect(eventReceived).toBe(true) - expect(receivedInfo).toBeDefined() - expect(receivedInfo?.id).toBe(info.id) - expect(receivedInfo?.projectID).toBe(info.projectID) - expect(receivedInfo?.directory).toBe(info.directory) - expect(receivedInfo?.path).toBe(info.path) - expect(receivedInfo?.title).toBe(info.title) - - await remove(info.id) - }, - }) - }) - - test("session.created event should be emitted before session.updated", async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const events: string[] = [] - - const unsubCreated = Bus.subscribe(SessionNs.Event.Created, () => { - events.push("created") - }) + const unsub = subscribeGlobal(SessionNs.Event.Created.type, (event) => { + Deferred.doneUnsafe(received, Effect.succeed(event.properties.info as SessionNs.Info)) + }) + yield* Effect.addFinalizer(() => Effect.sync(unsub)) - const unsubUpdated = Bus.subscribe(SessionNs.Event.Updated, () => { - events.push("updated") - }) + const info = yield* session.create({}) + const receivedInfo = yield* awaitDeferred(received, "timed out waiting for session.created") - const info = await create({}) - await new Promise((resolve) => setTimeout(resolve, 100)) - unsubCreated() - unsubUpdated() + expect(receivedInfo.id).toBe(info.id) + expect(receivedInfo.projectID).toBe(info.projectID) + expect(receivedInfo.directory).toBe(info.directory) + expect(receivedInfo.path).toBe(info.path) + expect(receivedInfo.title).toBe(info.title) - expect(events).toContain("created") - expect(events).toContain("updated") - expect(events.indexOf("created")).toBeLessThan(events.indexOf("updated")) + yield* session.remove(info.id) + }), + ) + + it.instance("session.created event should be emitted before session.updated", () => + Effect.gen(function* () { + if (Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return + + const session = yield* SessionNs.Service + const events: string[] = [] + const received = yield* Deferred.make() + const push = (event: string) => { + events.push(event) + if (events.includes("created") && events.includes("updated")) { + Deferred.doneUnsafe(received, Effect.succeed(events)) + } + } + + const unsubCreated = subscribeGlobal(SessionNs.Event.Created.type, () => { + push("created") + }) + yield* Effect.addFinalizer(() => Effect.sync(unsubCreated)) - await remove(info.id) - }, - }) - }) + const unsubUpdated = subscribeGlobal(SessionNs.Event.Updated.type, () => { + push("updated") + }) + yield* Effect.addFinalizer(() => Effect.sync(unsubUpdated)) + + const info = yield* session.create({}) + const receivedEvents = yield* awaitDeferred(received, "timed out waiting for session created/updated events") + + expect(receivedEvents).toContain("created") + expect(receivedEvents).toContain("updated") + expect(receivedEvents.indexOf("created")).toBeLessThan(receivedEvents.indexOf("updated")) + + yield* session.remove(info.id) + }), + ) }) describe("step-finish token propagation via Bus event", () => { - test( + it.instance( "non-zero tokens propagate through PartUpdated event", - async () => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const info = await create({}) - - const messageID = MessageID.ascending() - await updateMessage({ - id: messageID, - sessionID: info.id, - role: "user", - time: { created: Date.now() }, - agent: "user", - model: { providerID: "test", modelID: "test" }, - tools: {}, - mode: "", - } as unknown as MessageV2.Info) - - // Bus subscribers receive readonly Schema.Type payloads; `MessageV2.Part` - // is the mutable domain type. Cast bridges the two — safe because the - // test only reads the value afterwards. - let received: MessageV2.Part | undefined - const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, (event) => { - received = event.properties.part as MessageV2.Part - }) - - const tokens = { - total: 1500, - input: 500, - output: 800, - reasoning: 200, - cache: { read: 100, write: 50 }, - } - - const partInput = { - id: PartID.ascending(), - messageID, - sessionID: info.id, - type: "step-finish" as const, - reason: "stop", - cost: 0.005, - tokens, - } - - await updatePart(partInput) - await new Promise((resolve) => setTimeout(resolve, 100)) - - expect(received).toBeDefined() - expect(received!.type).toBe("step-finish") - const finish = received as MessageV2.StepFinishPart - expect(finish.tokens.input).toBe(500) - expect(finish.tokens.output).toBe(800) - expect(finish.tokens.reasoning).toBe(200) - expect(finish.tokens.total).toBe(1500) - expect(finish.tokens.cache.read).toBe(100) - expect(finish.tokens.cache.write).toBe(50) - expect(finish.cost).toBe(0.005) - expect(received).not.toBe(partInput) - - unsub() - await remove(info.id) - }, - }) - }, + () => + Effect.gen(function* () { + const session = yield* SessionNs.Service + const info = yield* session.create({}) + + const messageID = MessageID.ascending() + yield* session.updateMessage({ + id: messageID, + sessionID: info.id, + role: "user", + time: { created: Date.now() }, + agent: "user", + model: { providerID: "test", modelID: "test" }, + tools: {}, + mode: "", + } as unknown as MessageV2.Info) + + // Bus subscribers receive readonly Schema.Type payloads; `MessageV2.Part` + // is the mutable domain type. Cast bridges the two — safe because the + // test only reads the value afterwards. + const received = yield* Deferred.make() + const unsub = subscribeGlobal(MessageV2.Event.PartUpdated.type, (event) => { + Deferred.doneUnsafe(received, Effect.succeed(event.properties.part as MessageV2.Part)) + }) + yield* Effect.addFinalizer(() => Effect.sync(unsub)) + + const tokens = { + total: 1500, + input: 500, + output: 800, + reasoning: 200, + cache: { read: 100, write: 50 }, + } + + const partInput = { + id: PartID.ascending(), + messageID, + sessionID: info.id, + type: "step-finish" as const, + reason: "stop", + cost: 0.005, + tokens, + } + + yield* session.updatePart(partInput) + const receivedPart = yield* awaitDeferred(received, "timed out waiting for message.part.updated") + + expect(receivedPart.type).toBe("step-finish") + const finish = receivedPart as MessageV2.StepFinishPart + expect(finish.tokens.input).toBe(500) + expect(finish.tokens.output).toBe(800) + expect(finish.tokens.reasoning).toBe(200) + expect(finish.tokens.total).toBe(1500) + expect(finish.tokens.cache.read).toBe(100) + expect(finish.tokens.cache.write).toBe(50) + expect(finish.cost).toBe(0.005) + expect(receivedPart).not.toBe(partInput) + + yield* session.remove(info.id) + }), { timeout: 30000 }, ) }) describe("Session", () => { - test("remove works without an instance", async () => { - await using tmp = await tmpdir({ git: true }) - - const info = await WithInstance.provide({ - directory: tmp.path, - fn: () => create({ title: "remove-without-instance" }), - }) - - await expect(async () => { - await remove(info.id) - }).not.toThrow() - - let missing = false - await get(info.id).catch(() => { - missing = true - }) - - expect(missing).toBe(true) - }) + it.live("remove works without an instance", () => + Effect.gen(function* () { + const session = yield* SessionNs.Service + const dir = yield* tmpdirScoped({ git: true }) + const info = yield* provideInstance(dir)(session.create({ title: "remove-without-instance" })) + + const removeExit = yield* remove(info.id).pipe(Effect.exit) + expect(Exit.isSuccess(removeExit)).toBe(true) + + const getExit = yield* session.get(info.id).pipe(Effect.exit) + expect(Exit.isFailure(getExit)).toBe(true) + }), + ) }) From f3c91c5f96dc959f8a9aab6b4793d116ae83ab8d Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 18:52:25 +0000 Subject: [PATCH 124/378] chore: generate --- packages/opencode/test/agent/agent.test.ts | 12 ++-- .../test/tool/external-directory.test.ts | 72 ++++++++++--------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 7fd489150..9893ee822 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -105,9 +105,9 @@ it.instance("explore agent asks for external directories and allows whitelisted expect(explore).toBeDefined() expect(Permission.evaluate("external_directory", "/some/other/path", explore!.permission).action).toBe("ask") expect(Permission.evaluate("external_directory", Truncate.GLOB, explore!.permission).action).toBe("allow") - expect(Permission.evaluate("external_directory", path.join(Global.Path.tmp, "agent-work"), explore!.permission).action).toBe( - "allow", - ) + expect( + Permission.evaluate("external_directory", path.join(Global.Path.tmp, "agent-work"), explore!.permission).action, + ).toBe("allow") }), ) @@ -548,9 +548,9 @@ it.instance( it.instance("global tmp directory children are allowed for external_directory", () => Effect.gen(function* () { const build = yield* load((svc) => svc.get("build")) - expect(Permission.evaluate("external_directory", path.join(Global.Path.tmp, "scratch"), build!.permission).action).toBe( - "allow", - ) + expect( + Permission.evaluate("external_directory", path.join(Global.Path.tmp, "scratch"), build!.permission).action, + ).toBe("allow") expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("ask") }), ) diff --git a/packages/opencode/test/tool/external-directory.test.ts b/packages/opencode/test/tool/external-directory.test.ts index 7585ff98f..04ef5c5d0 100644 --- a/packages/opencode/test/tool/external-directory.test.ts +++ b/packages/opencode/test/tool/external-directory.test.ts @@ -107,46 +107,50 @@ describe("tool.assertExternalDirectory", () => { ) if (process.platform === "win32") { - it.instance("normalizes Windows path variants to one glob", () => - Effect.gen(function* () { - const { requests, ctx } = makeCtx() - - const outerTmp = yield* tmpdirScoped() - yield* Effect.promise(() => Bun.write(path.join(outerTmp, "outside.txt"), "x")) - - const target = path.join(outerTmp, "outside.txt") - const alt = target - .replace(/^[A-Za-z]:/, "") - .replaceAll("\\", "/") - .toLowerCase() - - yield* assertExternalDirectoryEffect(ctx, alt) - - const req = requests.find((r) => r.permission === "external_directory") - const expected = glob(path.join(outerTmp, "*")) - expect(req).toBeDefined() - expect(req!.patterns).toEqual([expected]) - expect(req!.always).toEqual([expected]) - }), + it.instance( + "normalizes Windows path variants to one glob", + () => + Effect.gen(function* () { + const { requests, ctx } = makeCtx() + + const outerTmp = yield* tmpdirScoped() + yield* Effect.promise(() => Bun.write(path.join(outerTmp, "outside.txt"), "x")) + + const target = path.join(outerTmp, "outside.txt") + const alt = target + .replace(/^[A-Za-z]:/, "") + .replaceAll("\\", "/") + .toLowerCase() + + yield* assertExternalDirectoryEffect(ctx, alt) + + const req = requests.find((r) => r.permission === "external_directory") + const expected = glob(path.join(outerTmp, "*")) + expect(req).toBeDefined() + expect(req!.patterns).toEqual([expected]) + expect(req!.always).toEqual([expected]) + }), { git: true }, ) - it.instance("uses drive root glob for root files", () => - Effect.gen(function* () { - const { requests, ctx } = makeCtx() + it.instance( + "uses drive root glob for root files", + () => + Effect.gen(function* () { + const { requests, ctx } = makeCtx() - const tmp = yield* TestInstance - const root = path.parse(tmp.directory).root - const target = path.join(root, "boot.ini") + const tmp = yield* TestInstance + const root = path.parse(tmp.directory).root + const target = path.join(root, "boot.ini") - yield* assertExternalDirectoryEffect(ctx, target) + yield* assertExternalDirectoryEffect(ctx, target) - const req = requests.find((r) => r.permission === "external_directory") - const expected = path.join(root, "*") - expect(req).toBeDefined() - expect(req!.patterns).toEqual([expected]) - expect(req!.always).toEqual([expected]) - }), + const req = requests.find((r) => r.permission === "external_directory") + const expected = path.join(root, "*") + expect(req).toBeDefined() + expect(req!.patterns).toEqual([expected]) + expect(req!.always).toEqual([expected]) + }), { git: true }, ) } From e540daabc4ca21df3871cc0e2e3cc570d9157c60 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 14:56:01 -0400 Subject: [PATCH 125/378] test(agent): migrate plan bypass tests to Effect runner (#27119) --- .../agent/plan-mode-subagent-bypass.test.ts | 144 ++++++++---------- 1 file changed, 64 insertions(+), 80 deletions(-) diff --git a/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts b/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts index 5ba6b5483..255aea12e 100644 --- a/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts +++ b/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts @@ -18,110 +18,85 @@ * permissions are passed through, and Plan Mode's restrictions live on the * agent, not the session. */ -import { test, expect, afterEach } from "bun:test" +import { expect } from "bun:test" import { Effect } from "effect" -import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture" -import { WithInstance } from "../../src/project/with-instance" import { Agent } from "../../src/agent/agent" import { deriveSubagentSessionPermission } from "../../src/agent/subagent-permissions" import { Permission } from "../../src/permission" +import { testEffect } from "../lib/effect" -afterEach(async () => { - await disposeAllInstances() -}) - -function load(dir: string, fn: (svc: Agent.Interface) => Effect.Effect) { - return Effect.runPromise(provideInstance(dir)(Agent.Service.use(fn)).pipe(Effect.provide(Agent.defaultLayer))) -} +const it = testEffect(Agent.defaultLayer) // `deriveSubagentSessionPermission` is imported from production. The test // exercises the actual helper that task.ts uses to build the subagent's // session permission, so any regression in that helper trips this test. -test("[#26514] subagent spawned from plan mode inherits read-only restriction (edit denied)", async () => { - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const planAgent = await load(tmp.path, (svc) => svc.get("plan")) - const generalAgent = await load(tmp.path, (svc) => svc.get("general")) +it.instance("[#26514] subagent spawned from plan mode inherits read-only restriction (edit denied)", () => + Effect.gen(function* () { + const planAgent = yield* Agent.Service.use((svc) => svc.get("plan")) + const generalAgent = yield* Agent.Service.use((svc) => svc.get("general")) - expect(planAgent).toBeDefined() - expect(generalAgent).toBeDefined() - // Sanity: the plan agent itself blocks edit. (Note: `write` and - // `apply_patch` route through the `edit` permission at the runtime - // tool layer — see Permission.disabled / EDIT_TOOLS.) - expect(Permission.evaluate("edit", "/some/file.ts", planAgent!.permission).action).toBe("deny") + expect(planAgent).toBeDefined() + expect(generalAgent).toBeDefined() + // Sanity: the plan agent itself blocks edit. (Note: `write` and + // `apply_patch` route through the `edit` permission at the runtime + // tool layer — see Permission.disabled / EDIT_TOOLS.) + expect(Permission.evaluate("edit", "/some/file.ts", planAgent!.permission).action).toBe("deny") - // Simulate the plan-mode parent session: in real flow the plan - // session's `permission` field is empty (Plan Mode lives on the agent - // ruleset, not the session). So we pass [] through as the parent - // session permission, exactly like the actual code path. - const parentSessionPermission: Permission.Ruleset = [] + // Simulate the plan-mode parent session: in real flow the plan + // session's `permission` field is empty (Plan Mode lives on the agent + // ruleset, not the session). So we pass [] through as the parent + // session permission, exactly like the actual code path. + const parentSessionPermission: Permission.Ruleset = [] - const subagentSessionPermission = deriveSubagentSessionPermission({ - parentSessionPermission, - parentAgent: planAgent, - subagent: generalAgent!, - }) + const subagentSessionPermission = deriveSubagentSessionPermission({ + parentSessionPermission, + parentAgent: planAgent, + subagent: generalAgent!, + }) - // Mirror the runtime evaluation in session/prompt.ts (~line 410, 639): - // ruleset: Permission.merge(agent.permission, session.permission ?? []) - const effective = Permission.merge(generalAgent!.permission, subagentSessionPermission) + // Mirror the runtime evaluation in session/prompt.ts (~line 410, 639): + // ruleset: Permission.merge(agent.permission, session.permission ?? []) + const effective = Permission.merge(generalAgent!.permission, subagentSessionPermission) - expect(Permission.evaluate("edit", "/some/file.ts", effective).action).toBe("deny") - expect(Permission.evaluate("edit", "/another/path/index.tsx", effective).action).toBe("deny") - }, - }) -}) + expect(Permission.evaluate("edit", "/some/file.ts", effective).action).toBe("deny") + expect(Permission.evaluate("edit", "/another/path/index.tsx", effective).action).toBe("deny") + }), +) -test("[#26514] explore subagent launched from plan mode also stays read-only", async () => { +it.instance("[#26514] explore subagent launched from plan mode also stays read-only", () => // Sibling check: even though `explore` is intrinsically read-only, the // bug surface is the same. Including this case to document that the fix // should propagate the parent **agent** permissions, not just deny edit // when the subagent happens to already deny it. - await using tmp = await tmpdir() - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const planAgent = await load(tmp.path, (svc) => svc.get("plan")) - const explore = await load(tmp.path, (svc) => svc.get("explore")) - expect(planAgent).toBeDefined() - expect(explore).toBeDefined() + Effect.gen(function* () { + const planAgent = yield* Agent.Service.use((svc) => svc.get("plan")) + const explore = yield* Agent.Service.use((svc) => svc.get("explore")) + expect(planAgent).toBeDefined() + expect(explore).toBeDefined() - const parentSessionPermission: Permission.Ruleset = [] - const subagentSessionPermission = deriveSubagentSessionPermission({ - parentSessionPermission, - parentAgent: planAgent, - subagent: explore!, - }) - const effective = Permission.merge(explore!.permission, subagentSessionPermission) + const parentSessionPermission: Permission.Ruleset = [] + const subagentSessionPermission = deriveSubagentSessionPermission({ + parentSessionPermission, + parentAgent: planAgent, + subagent: explore!, + }) + const effective = Permission.merge(explore!.permission, subagentSessionPermission) - // Already deny — sanity check. - expect(Permission.evaluate("edit", "/x.ts", effective).action).toBe("deny") - }, - }) -}) + // Already deny — sanity check. + expect(Permission.evaluate("edit", "/x.ts", effective).action).toBe("deny") + }), +) -test("[#26514] custom user subagent launched from plan mode bypasses Plan Mode read-only", async () => { +it.instance( + "[#26514] custom user subagent launched from plan mode bypasses Plan Mode read-only", // The most damaging case: a user-defined subagent with default // permissions (allow-by-default, like `general`). The subagent must NOT // be able to edit when the parent agent is `plan`. - await using tmp = await tmpdir({ - config: { - agent: { - my_subagent: { - description: "A user-defined subagent", - mode: "subagent", - }, - }, - }, - }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const planAgent = await load(tmp.path, (svc) => svc.get("plan")) - const my = await load(tmp.path, (svc) => svc.get("my_subagent")) + () => + Effect.gen(function* () { + const planAgent = yield* Agent.Service.use((svc) => svc.get("plan")) + const my = yield* Agent.Service.use((svc) => svc.get("my_subagent")) expect(planAgent).toBeDefined() expect(my).toBeDefined() @@ -136,6 +111,15 @@ test("[#26514] custom user subagent launched from plan mode bypasses Plan Mode r // BUG: on origin/dev edit resolves to "allow" because the plan // agent's `edit: deny *` rule never reaches the subagent. expect(Permission.evaluate("edit", "/some/file.ts", effective).action).toBe("deny") + }), + { + config: { + agent: { + my_subagent: { + description: "A user-defined subagent", + mode: "subagent", + }, + }, }, - }) -}) + }, +) From 45de4975dec676e398a308149ae62bac5e017ce8 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 13 May 2026 01:08:30 +0530 Subject: [PATCH 126/378] refactor(core): resolve default agent info (#27125) --- packages/opencode/src/acp/agent.ts | 6 +++--- packages/opencode/src/agent/agent.ts | 15 ++++++++++++--- .../instance/httpapi/handlers/experimental.ts | 2 +- packages/opencode/src/session/prompt.ts | 10 +++++----- packages/opencode/test/agent/agent.test.ts | 8 ++++++++ packages/opencode/test/tool/registry.test.ts | 2 +- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 867b830cf..a8eacf835 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -1094,8 +1094,8 @@ export class Agent implements ACPAgent { const currentModeId = await (async () => { if (!availableModes.length) return undefined - const defaultAgentName = await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultAgent())) - const resolvedModeId = availableModes.find((mode) => mode.name === defaultAgentName)?.id ?? availableModes[0].id + const defaultAgent = await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultInfo())) + const resolvedModeId = availableModes.find((mode) => mode.name === defaultAgent.name)?.id ?? availableModes[0].id this.sessionManager.setMode(sessionId, resolvedModeId) return resolvedModeId })() @@ -1328,7 +1328,7 @@ export class Agent implements ACPAgent { if (!current) { this.sessionManager.setModel(session.id, model) } - const agent = session.modeId ?? (await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultAgent()))) + const agent = session.modeId ?? (await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultInfo()))).name const parts: Array< | { type: "text"; text: string; synthetic?: boolean; ignored?: boolean } diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index c1a644282..423a51318 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -57,6 +57,7 @@ const GeneratedAgent = Schema.Struct({ export interface Interface { readonly get: (agent: string) => Effect.Effect readonly list: () => Effect.Effect + readonly defaultInfo: () => Effect.Effect readonly defaultAgent: () => Effect.Effect readonly generate: (input: { description: string @@ -333,23 +334,28 @@ export const layer = Layer.effect( ) }) - const defaultAgent = Effect.fnUntraced(function* () { + const defaultInfo = Effect.fnUntraced(function* () { const c = yield* config.get() if (c.default_agent) { const agent = agents[c.default_agent] if (!agent) throw new Error(`default agent "${c.default_agent}" not found`) if (agent.mode === "subagent") throw new Error(`default agent "${c.default_agent}" is a subagent`) if (agent.hidden === true) throw new Error(`default agent "${c.default_agent}" is hidden`) - return agent.name + return agent } const visible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true) if (!visible) throw new Error("no primary visible agent found") - return visible.name + return visible + }) + + const defaultAgent = Effect.fnUntraced(function* () { + return (yield* defaultInfo()).name }) return { get, list, + defaultInfo, defaultAgent, } satisfies State }), @@ -362,6 +368,9 @@ export const layer = Layer.effect( list: Effect.fn("Agent.list")(function* () { return yield* InstanceState.useEffect(state, (s) => s.list()) }), + defaultInfo: Effect.fn("Agent.defaultInfo")(function* () { + return yield* InstanceState.useEffect(state, (s) => s.defaultInfo()) + }), defaultAgent: Effect.fn("Agent.defaultAgent")(function* () { return yield* InstanceState.useEffect(state, (s) => s.defaultAgent()) }), diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts index 360daf54a..9cf668ceb 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/experimental.ts @@ -79,7 +79,7 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper const list = yield* registry.tools({ providerID: ctx.query.provider, modelID: ctx.query.model, - agent: yield* agents.get(yield* agents.defaultAgent()), + agent: yield* agents.defaultInfo(), }) return list.map((item) => ({ id: item.id, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 15246dac3..b89561d5d 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1083,8 +1083,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the }) const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) { - const agentName = input.agent || (yield* agents.defaultAgent()) - const ag = yield* agents.get(agentName) + const agentName = input.agent + const ag = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo() if (!ag) { const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name) const hint = available.length ? ` Available agents: ${available.join(", ")}` : "" @@ -1875,7 +1875,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() }) throw error } - const agentName = cmd.agent ?? input.agent ?? (yield* agents.defaultAgent()) + const agentName = cmd.agent ?? input.agent const raw = input.arguments.match(argsRegex) ?? [] const args = raw.map((arg) => arg.replace(quoteTrimRegex, "")) @@ -1928,7 +1928,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the yield* getModel(taskModel.providerID, taskModel.modelID, input.sessionID) - const agent = yield* agents.get(agentName) + const agent = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo() if (!agent) { const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name) const hint = available.length ? ` Available agents: ${available.join(", ")}` : "" @@ -1952,7 +1952,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the ] : [...templateParts, ...(input.parts ?? [])] - const userAgent = isSubtask ? (input.agent ?? (yield* agents.defaultAgent())) : agentName + const userAgent = isSubtask ? (input.agent ?? (yield* agents.defaultInfo()).name) : agent.name const userModel = isSubtask ? input.model ? Provider.parseModel(input.model) diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 9893ee822..a781a855c 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -638,6 +638,14 @@ it.instance("defaultAgent returns build when no default_agent config", () => }), ) +it.instance("defaultInfo returns resolved build agent when no default_agent config", () => + Effect.gen(function* () { + const agent = yield* load((svc) => svc.defaultInfo()) + expect(agent.name).toBe("build") + expect(agent.mode).toBe("primary") + }), +) + it.instance( "defaultAgent respects default_agent config set to plan", () => diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 5ee56300c..fb4dd31a5 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -180,7 +180,7 @@ describe("tool.registry", () => { const promptTools = yield* registry.tools({ providerID: ProviderID.opencode, modelID: ModelID.make("test"), - agent: yield* agents.get(yield* agents.defaultAgent()), + agent: yield* agents.defaultInfo(), }) const promptTool = promptTools.find((tool) => tool.id === "sql") if (!promptTool) throw new Error("custom sql tool was not returned for prompts") From ec960da42a2f3fef7523cbe026208b8c85323d40 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 15:39:03 -0400 Subject: [PATCH 127/378] test(skill): migrate discovery tests to Effect runner (#27127) --- .../opencode/test/skill/discovery.test.ts | 145 ++++++++++-------- 1 file changed, 80 insertions(+), 65 deletions(-) diff --git a/packages/opencode/test/skill/discovery.test.ts b/packages/opencode/test/skill/discovery.test.ts index f4a17f25c..43018d9a4 100644 --- a/packages/opencode/test/skill/discovery.test.ts +++ b/packages/opencode/test/skill/discovery.test.ts @@ -1,10 +1,11 @@ -import { describe, test, expect, beforeAll, afterAll } from "bun:test" +import { describe, expect, beforeAll, afterAll } from "bun:test" import { Effect } from "effect" import { Discovery } from "../../src/skill/discovery" import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util/filesystem" import { rm } from "fs/promises" import path from "path" +import { testEffect } from "../lib/effect" let CLOUDFLARE_SKILLS_URL: string let server: ReturnType @@ -12,6 +13,7 @@ let downloadCount = 0 const fixturePath = path.join(import.meta.dir, "../fixture/skills") const cacheDir = path.join(Global.Path.cache, "skills") +const it = testEffect(Discovery.defaultLayer) beforeAll(async () => { await rm(cacheDir, { recursive: true, force: true }) @@ -47,70 +49,83 @@ afterAll(async () => { }) describe("Discovery.pull", () => { - const pull = (url: string) => - Effect.runPromise(Discovery.Service.use((s) => s.pull(url)).pipe(Effect.provide(Discovery.defaultLayer))) - - test("downloads skills from cloudflare url", async () => { - const dirs = await pull(CLOUDFLARE_SKILLS_URL) - expect(dirs.length).toBeGreaterThan(0) - for (const dir of dirs) { - expect(dir).toStartWith(cacheDir) - const md = path.join(dir, "SKILL.md") - expect(await Filesystem.exists(md)).toBe(true) - } + const pull = Effect.fn("DiscoveryTest.pull")(function* (url: string) { + return yield* Discovery.Service.use((s) => s.pull(url)) }) - test("url without trailing slash works", async () => { - const dirs = await pull(CLOUDFLARE_SKILLS_URL.replace(/\/$/, "")) - expect(dirs.length).toBeGreaterThan(0) - for (const dir of dirs) { - const md = path.join(dir, "SKILL.md") - expect(await Filesystem.exists(md)).toBe(true) - } - }) - - test("returns empty array for invalid url", async () => { - const dirs = await pull(`http://localhost:${server.port}/invalid-url/`) - expect(dirs).toEqual([]) - }) - - test("returns empty array for non-json response", async () => { - // any url not explicitly handled in server returns 404 text "Not Found" - const dirs = await pull(`http://localhost:${server.port}/some-other-path/`) - expect(dirs).toEqual([]) - }) - - test("downloads reference files alongside SKILL.md", async () => { - const dirs = await pull(CLOUDFLARE_SKILLS_URL) - // find a skill dir that should have reference files (e.g. agents-sdk) - const agentsSdk = dirs.find((d) => d.endsWith(path.sep + "agents-sdk")) - expect(agentsSdk).toBeDefined() - if (agentsSdk) { - const refs = path.join(agentsSdk, "references") - expect(await Filesystem.exists(path.join(agentsSdk, "SKILL.md"))).toBe(true) - // agents-sdk has reference files per the index - const refDir = await Array.fromAsync(new Bun.Glob("**/*.md").scan({ cwd: refs, onlyFiles: true })) - expect(refDir.length).toBeGreaterThan(0) - } - }) - - test("caches downloaded files on second pull", async () => { - // clear dir and downloadCount - await rm(cacheDir, { recursive: true, force: true }) - downloadCount = 0 - - // first pull to populate cache - const first = await pull(CLOUDFLARE_SKILLS_URL) - expect(first.length).toBeGreaterThan(0) - const firstCount = downloadCount - expect(firstCount).toBeGreaterThan(0) - - // second pull should return same results from cache - const second = await pull(CLOUDFLARE_SKILLS_URL) - expect(second.length).toBe(first.length) - expect(second.sort()).toEqual(first.sort()) - - // second pull should NOT increment download count - expect(downloadCount).toBe(firstCount) - }) + it.live("downloads skills from cloudflare url", () => + Effect.gen(function* () { + const dirs = yield* pull(CLOUDFLARE_SKILLS_URL) + expect(dirs.length).toBeGreaterThan(0) + for (const dir of dirs) { + expect(dir).toStartWith(cacheDir) + const md = path.join(dir, "SKILL.md") + expect(yield* Effect.promise(() => Filesystem.exists(md))).toBe(true) + } + }), + ) + + it.live("url without trailing slash works", () => + Effect.gen(function* () { + const dirs = yield* pull(CLOUDFLARE_SKILLS_URL.replace(/\/$/, "")) + expect(dirs.length).toBeGreaterThan(0) + for (const dir of dirs) { + const md = path.join(dir, "SKILL.md") + expect(yield* Effect.promise(() => Filesystem.exists(md))).toBe(true) + } + }), + ) + + it.live("returns empty array for invalid url", () => + Effect.gen(function* () { + const dirs = yield* pull(`http://localhost:${server.port}/invalid-url/`) + expect(dirs).toEqual([]) + }), + ) + + it.live("returns empty array for non-json response", () => + Effect.gen(function* () { + // any url not explicitly handled in server returns 404 text "Not Found" + const dirs = yield* pull(`http://localhost:${server.port}/some-other-path/`) + expect(dirs).toEqual([]) + }), + ) + + it.live("downloads reference files alongside SKILL.md", () => + Effect.gen(function* () { + const dirs = yield* pull(CLOUDFLARE_SKILLS_URL) + // find a skill dir that should have reference files (e.g. agents-sdk) + const agentsSdk = dirs.find((d) => d.endsWith(path.sep + "agents-sdk")) + expect(agentsSdk).toBeDefined() + if (agentsSdk) { + const refs = path.join(agentsSdk, "references") + expect(yield* Effect.promise(() => Filesystem.exists(path.join(agentsSdk, "SKILL.md")))).toBe(true) + // agents-sdk has reference files per the index + const refDir = yield* Effect.promise(() => Array.fromAsync(new Bun.Glob("**/*.md").scan({ cwd: refs, onlyFiles: true }))) + expect(refDir.length).toBeGreaterThan(0) + } + }), + ) + + it.live("caches downloaded files on second pull", () => + Effect.gen(function* () { + // clear dir and downloadCount + yield* Effect.promise(() => rm(cacheDir, { recursive: true, force: true })) + downloadCount = 0 + + // first pull to populate cache + const first = yield* pull(CLOUDFLARE_SKILLS_URL) + expect(first.length).toBeGreaterThan(0) + const firstCount = downloadCount + expect(firstCount).toBeGreaterThan(0) + + // second pull should return same results from cache + const second = yield* pull(CLOUDFLARE_SKILLS_URL) + expect(second.length).toBe(first.length) + expect(second.sort()).toEqual(first.sort()) + + // second pull should NOT increment download count + expect(downloadCount).toBe(firstCount) + }), + ) }) From 3e2ec192cf06970bd51722918070134f5ab4e7b1 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 15:40:01 -0400 Subject: [PATCH 128/378] test(question): remove WithInstance bridge (#27128) --- packages/opencode/test/question/question.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/opencode/test/question/question.test.ts b/packages/opencode/test/question/question.test.ts index 4e2c8ef9b..a87907c57 100644 --- a/packages/opencode/test/question/question.test.ts +++ b/packages/opencode/test/question/question.test.ts @@ -2,7 +2,6 @@ import { afterEach, expect } from "bun:test" import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { Question } from "../../src/question" import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { InstanceRuntime } from "../../src/project/instance-runtime" import { QuestionID } from "../../src/question/schema" import { disposeAllInstances, provideInstance, reloadTestInstance, tmpdirScoped } from "../fixture/fixture" @@ -398,9 +397,7 @@ it.live("pending question rejects on instance dispose", () => }).pipe(provideInstance(dir), Effect.forkScoped) expect(yield* waitForPending(1).pipe(provideInstance(dir))).toHaveLength(1) - yield* Effect.promise(() => - WithInstance.provide({ directory: dir, fn: () => InstanceRuntime.disposeInstance(Instance.current) }), - ) + yield* Effect.promise(() => InstanceRuntime.disposeInstance(Instance.current)).pipe(provideInstance(dir)) const exit = yield* Fiber.await(fiber) expect(Exit.isFailure(exit)).toBe(true) From fec78154b5449f8995d2676853769643eef8ce4d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 15:41:24 -0400 Subject: [PATCH 129/378] test(bus): migrate bus tests to Effect runner (#27131) --- packages/opencode/test/bus/bus.test.ts | 350 +++++++++++++------------ 1 file changed, 185 insertions(+), 165 deletions(-) diff --git a/packages/opencode/test/bus/bus.test.ts b/packages/opencode/test/bus/bus.test.ts index 876cb1ed7..084498616 100644 --- a/packages/opencode/test/bus/bus.test.ts +++ b/packages/opencode/test/bus/bus.test.ts @@ -1,220 +1,240 @@ -import { afterEach, describe, expect, test } from "bun:test" -import { Schema } from "effect" +import { afterEach, describe, expect } from "bun:test" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { Deferred, Effect, Layer, Schema } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" const TestEvent = { Ping: BusEvent.define("test.ping", Schema.Struct({ value: Schema.Number })), Pong: BusEvent.define("test.pong", Schema.Struct({ message: Schema.String })), } -function withInstance(directory: string, fn: () => Promise) { - return WithInstance.provide({ directory, fn }) -} +const it = testEffect(Layer.mergeAll(Bus.layer, CrossSpawnSpawner.defaultLayer)) describe("Bus", () => { afterEach(() => disposeAllInstances()) describe("publish + subscribe", () => { - test("subscriber is live immediately after subscribe returns", async () => { - await using tmp = await tmpdir() - const received: number[] = [] + it.instance("subscriber is live immediately after subscribe returns", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const received: number[] = [] + const done = yield* Deferred.make() - await withInstance(tmp.path, async () => { - Bus.subscribe(TestEvent.Ping, (evt) => { + yield* bus.subscribeCallback(TestEvent.Ping, (evt) => { received.push(evt.properties.value) + Deferred.doneUnsafe(done, Effect.void) }) - await Bus.publish(TestEvent.Ping, { value: 42 }) - await Bun.sleep(10) - }) + yield* bus.publish(TestEvent.Ping, { value: 42 }) + yield* Deferred.await(done).pipe(Effect.timeout("2 seconds")) - expect(received).toEqual([42]) - }) + expect(received).toEqual([42]) + }), + ) - test("subscriber receives matching events", async () => { - await using tmp = await tmpdir() - const received: number[] = [] + it.instance("subscriber receives matching events", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const received: number[] = [] + const done = yield* Deferred.make() - await withInstance(tmp.path, async () => { - Bus.subscribe(TestEvent.Ping, (evt) => { + yield* bus.subscribeCallback(TestEvent.Ping, (evt) => { received.push(evt.properties.value) + if (received.length === 2) Deferred.doneUnsafe(done, Effect.void) }) - // Give the subscriber fiber time to start consuming - await Bun.sleep(10) - await Bus.publish(TestEvent.Ping, { value: 42 }) - await Bus.publish(TestEvent.Ping, { value: 99 }) - // Give subscriber time to process - await Bun.sleep(10) - }) - - expect(received).toEqual([42, 99]) - }) - - test("subscriber does not receive events of other types", async () => { - await using tmp = await tmpdir() - const pings: number[] = [] - - await withInstance(tmp.path, async () => { - Bus.subscribe(TestEvent.Ping, (evt) => { + yield* bus.publish(TestEvent.Ping, { value: 42 }) + yield* bus.publish(TestEvent.Ping, { value: 99 }) + yield* Deferred.await(done).pipe(Effect.timeout("2 seconds")) + + expect(received).toEqual([42, 99]) + }), + ) + + it.instance("subscriber does not receive events of other types", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const pings: number[] = [] + const done = yield* Deferred.make() + + yield* bus.subscribeCallback(TestEvent.Ping, (evt) => { pings.push(evt.properties.value) + Deferred.doneUnsafe(done, Effect.void) }) - await Bun.sleep(10) - await Bus.publish(TestEvent.Pong, { message: "hello" }) - await Bus.publish(TestEvent.Ping, { value: 1 }) - await Bun.sleep(10) - }) - - expect(pings).toEqual([1]) - }) - - test("publish with no subscribers does not throw", async () => { - await using tmp = await tmpdir() - - await withInstance(tmp.path, async () => { - await Bus.publish(TestEvent.Ping, { value: 1 }) - }) - }) + yield* bus.publish(TestEvent.Pong, { message: "hello" }) + yield* bus.publish(TestEvent.Ping, { value: 1 }) + yield* Deferred.await(done).pipe(Effect.timeout("2 seconds")) + + expect(pings).toEqual([1]) + }), + ) + + it.instance("publish with no subscribers does not throw", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + yield* bus.publish(TestEvent.Ping, { value: 1 }) + }), + ) }) describe("unsubscribe", () => { - test("unsubscribe stops delivery", async () => { - await using tmp = await tmpdir() - const received: number[] = [] + it.instance("unsubscribe stops delivery", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const received: number[] = [] + const first = yield* Deferred.make() - await withInstance(tmp.path, async () => { - const unsub = Bus.subscribe(TestEvent.Ping, (evt) => { + const unsub = yield* bus.subscribeCallback(TestEvent.Ping, (evt) => { received.push(evt.properties.value) + if (evt.properties.value === 1) Deferred.doneUnsafe(first, Effect.void) }) - await Bun.sleep(10) - await Bus.publish(TestEvent.Ping, { value: 1 }) - await Bun.sleep(10) - unsub() - await Bun.sleep(10) - await Bus.publish(TestEvent.Ping, { value: 2 }) - await Bun.sleep(10) - }) - - expect(received).toEqual([1]) - }) + yield* bus.publish(TestEvent.Ping, { value: 1 }) + yield* Deferred.await(first).pipe(Effect.timeout("2 seconds")) + yield* Effect.sync(unsub) + yield* bus.publish(TestEvent.Ping, { value: 2 }) + yield* Effect.sleep("10 millis") + + expect(received).toEqual([1]) + }), + ) }) describe("subscribeAll", () => { - test("subscribeAll is live immediately after subscribe returns", async () => { - await using tmp = await tmpdir() - const received: string[] = [] + it.instance("subscribeAll is live immediately after subscribe returns", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const received: string[] = [] + const done = yield* Deferred.make() - await withInstance(tmp.path, async () => { - Bus.subscribeAll((evt) => { + yield* bus.subscribeAllCallback((evt) => { received.push(evt.type) + Deferred.doneUnsafe(done, Effect.void) }) - await Bus.publish(TestEvent.Ping, { value: 1 }) - await Bun.sleep(10) - }) + yield* bus.publish(TestEvent.Ping, { value: 1 }) + yield* Deferred.await(done).pipe(Effect.timeout("2 seconds")) - expect(received).toEqual(["test.ping"]) - }) + expect(received).toEqual(["test.ping"]) + }), + ) - test("receives all event types", async () => { - await using tmp = await tmpdir() - const received: string[] = [] + it.instance("receives all event types", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const received: string[] = [] + const done = yield* Deferred.make() - await withInstance(tmp.path, async () => { - Bus.subscribeAll((evt) => { + yield* bus.subscribeAllCallback((evt) => { received.push(evt.type) + if (received.length === 2) Deferred.doneUnsafe(done, Effect.void) }) - await Bun.sleep(10) - await Bus.publish(TestEvent.Ping, { value: 1 }) - await Bus.publish(TestEvent.Pong, { message: "hi" }) - await Bun.sleep(10) - }) - - expect(received).toContain("test.ping") - expect(received).toContain("test.pong") - }) + yield* bus.publish(TestEvent.Ping, { value: 1 }) + yield* bus.publish(TestEvent.Pong, { message: "hi" }) + yield* Deferred.await(done).pipe(Effect.timeout("2 seconds")) + + expect(received).toContain("test.ping") + expect(received).toContain("test.pong") + }), + ) }) describe("multiple subscribers", () => { - test("all subscribers for same event type are called", async () => { - await using tmp = await tmpdir() - const a: number[] = [] - const b: number[] = [] - - await withInstance(tmp.path, async () => { - Bus.subscribe(TestEvent.Ping, (evt) => { + it.instance("all subscribers for same event type are called", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const a: number[] = [] + const b: number[] = [] + const doneA = yield* Deferred.make() + const doneB = yield* Deferred.make() + + yield* bus.subscribeCallback(TestEvent.Ping, (evt) => { a.push(evt.properties.value) + Deferred.doneUnsafe(doneA, Effect.void) }) - Bus.subscribe(TestEvent.Ping, (evt) => { + yield* bus.subscribeCallback(TestEvent.Ping, (evt) => { b.push(evt.properties.value) + Deferred.doneUnsafe(doneB, Effect.void) }) - await Bun.sleep(10) - await Bus.publish(TestEvent.Ping, { value: 7 }) - await Bun.sleep(10) - }) - - expect(a).toEqual([7]) - expect(b).toEqual([7]) - }) + yield* bus.publish(TestEvent.Ping, { value: 7 }) + yield* Deferred.await(doneA).pipe(Effect.timeout("2 seconds")) + yield* Deferred.await(doneB).pipe(Effect.timeout("2 seconds")) + + expect(a).toEqual([7]) + expect(b).toEqual([7]) + }), + ) }) describe("instance isolation", () => { - test("events in one directory do not reach subscribers in another", async () => { - await using tmpA = await tmpdir() - await using tmpB = await tmpdir() - const receivedA: number[] = [] - const receivedB: number[] = [] - - await withInstance(tmpA.path, async () => { - Bus.subscribe(TestEvent.Ping, (evt) => { - receivedA.push(evt.properties.value) - }) - await Bun.sleep(10) - }) - - await withInstance(tmpB.path, async () => { - Bus.subscribe(TestEvent.Ping, (evt) => { - receivedB.push(evt.properties.value) - }) - await Bun.sleep(10) - }) - - await withInstance(tmpA.path, async () => { - await Bus.publish(TestEvent.Ping, { value: 1 }) - await Bun.sleep(10) - }) - - await withInstance(tmpB.path, async () => { - await Bus.publish(TestEvent.Ping, { value: 2 }) - await Bun.sleep(10) - }) - - expect(receivedA).toEqual([1]) - expect(receivedB).toEqual([2]) - }) + it.live("events in one directory do not reach subscribers in another", () => + Effect.gen(function* () { + const tmpA = yield* tmpdirScoped() + const tmpB = yield* tmpdirScoped() + const receivedA: number[] = [] + const receivedB: number[] = [] + const doneA = yield* Deferred.make() + const doneB = yield* Deferred.make() + + yield* Effect.gen(function* () { + const bus = yield* Bus.Service + yield* bus.subscribeCallback(TestEvent.Ping, (evt) => { + receivedA.push(evt.properties.value) + Deferred.doneUnsafe(doneA, Effect.void) + }) + }).pipe(provideInstance(tmpA)) + + yield* Effect.gen(function* () { + const bus = yield* Bus.Service + yield* bus.subscribeCallback(TestEvent.Ping, (evt) => { + receivedB.push(evt.properties.value) + Deferred.doneUnsafe(doneB, Effect.void) + }) + }).pipe(provideInstance(tmpB)) + + yield* Effect.gen(function* () { + const bus = yield* Bus.Service + yield* bus.publish(TestEvent.Ping, { value: 1 }) + }).pipe(provideInstance(tmpA)) + + yield* Effect.gen(function* () { + const bus = yield* Bus.Service + yield* bus.publish(TestEvent.Ping, { value: 2 }) + }).pipe(provideInstance(tmpB)) + + yield* Deferred.await(doneA).pipe(Effect.timeout("2 seconds")) + yield* Deferred.await(doneB).pipe(Effect.timeout("2 seconds")) + + expect(receivedA).toEqual([1]) + expect(receivedB).toEqual([2]) + }), + ) }) describe("instance disposal", () => { - test("InstanceDisposed is delivered to wildcard subscribers before stream ends", async () => { - await using tmp = await tmpdir() - const received: string[] = [] - - await withInstance(tmp.path, async () => { - Bus.subscribeAll((evt) => { - received.push(evt.type) - }) - await Bun.sleep(10) - await Bus.publish(TestEvent.Ping, { value: 1 }) - await Bun.sleep(10) - }) - - // disposeAllInstances triggers the finalizer which publishes InstanceDisposed - await disposeAllInstances() - await Bun.sleep(50) - - expect(received).toContain("test.ping") - expect(received).toContain(Bus.InstanceDisposed.type) - }) + it.live("InstanceDisposed is delivered to wildcard subscribers before stream ends", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + const received: string[] = [] + const seen = yield* Deferred.make() + const disposed = yield* Deferred.make() + + yield* Effect.gen(function* () { + const bus = yield* Bus.Service + yield* bus.subscribeAllCallback((evt) => { + received.push(evt.type) + if (evt.type === TestEvent.Ping.type) Deferred.doneUnsafe(seen, Effect.void) + if (evt.type === Bus.InstanceDisposed.type) Deferred.doneUnsafe(disposed, Effect.void) + }) + yield* bus.publish(TestEvent.Ping, { value: 1 }) + yield* Deferred.await(seen).pipe(Effect.timeout("2 seconds")) + }).pipe(provideInstance(tmp)) + + yield* Effect.promise(disposeAllInstances) + yield* Deferred.await(disposed).pipe(Effect.timeout("2 seconds")) + + expect(received).toContain("test.ping") + expect(received).toContain(Bus.InstanceDisposed.type) + }), + ) }) }) From 71040c54aa89acbda7987dd2244091ae05e10cc8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 15:41:44 -0400 Subject: [PATCH 130/378] test(plugin): migrate loader shared tests to Effect runner (#27129) --- .../test/plugin/loader-shared.test.ts | 1080 +++++++++-------- 1 file changed, 586 insertions(+), 494 deletions(-) diff --git a/packages/opencode/test/plugin/loader-shared.test.ts b/packages/opencode/test/plugin/loader-shared.test.ts index 8c55950af..ffdb3291b 100644 --- a/packages/opencode/test/plugin/loader-shared.test.ts +++ b/packages/opencode/test/plugin/loader-shared.test.ts @@ -1,9 +1,11 @@ -import { afterAll, afterEach, describe, expect, spyOn, test } from "bun:test" +import { afterAll, afterEach, describe, expect, spyOn } from "bun:test" import { Effect, Layer } from "effect" import fs from "fs/promises" import path from "path" import { pathToFileURL } from "url" -import { disposeAllInstances, provideInstance, tmpdir } from "../fixture/fixture" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" import { Filesystem } from "@/util/filesystem" const disableDefault = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS @@ -28,38 +30,54 @@ afterEach(async () => { await disposeAllInstances() }) -async function load(dir: string) { +const it = testEffect(CrossSpawnSpawner.defaultLayer) + +function withTmp( + init: (dir: string) => Promise, + body: (tmp: { path: string; extra: T }) => Effect.Effect, +) { + return Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const extra = yield* Effect.promise(() => init(dir)) + return yield* body({ path: dir, extra }) + }) +} + +function load(dir: string) { const source = path.join(dir, "opencode.json") - const config = (await Bun.file(source).json()) as { plugin?: Array]> } - const plugins = config.plugin ?? [] return Effect.gen(function* () { - const plugin = yield* Plugin.Service - yield* plugin.list() - }).pipe( - Effect.provide( - Plugin.layer.pipe( - Layer.provide(Bus.layer), - Layer.provide( - TestConfig.layer({ - get: () => - Effect.succeed({ - plugin: plugins, - plugin_origins: plugins.map((plugin) => ({ spec: plugin, source, scope: "local" as const })), - }), - directories: () => Effect.succeed([dir]), - }), + const config = yield* Effect.promise( + () => Bun.file(source).json() as Promise<{ plugin?: Array]> }>, + ) + const plugins = config.plugin ?? [] + return yield* Effect.gen(function* () { + const plugin = yield* Plugin.Service + yield* plugin.list() + }).pipe( + Effect.provide( + Plugin.layer.pipe( + Layer.provide(Bus.layer), + Layer.provide( + TestConfig.layer({ + get: () => + Effect.succeed({ + plugin: plugins, + plugin_origins: plugins.map((plugin) => ({ spec: plugin, source, scope: "local" as const })), + }), + directories: () => Effect.succeed([dir]), + }), + ), ), ), - ), - provideInstance(dir), - Effect.runPromise, - ) + provideInstance(dir), + ) + }) } describe("plugin.loader.shared", () => { - test("loads a file:// plugin function export", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("loads a file:// plugin function export", () => + withTmp( + async (dir) => { const file = path.join(dir, "plugin.ts") const mark = path.join(dir, "called.txt") await Bun.write( @@ -80,15 +98,17 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - expect(await fs.readFile(tmp.extra.mark, "utf8")).toBe("called") - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + expect(yield* Effect.promise(() => fs.readFile(tmp.extra.mark, "utf8"))).toBe("called") + }), + ), + ) - test("deduplicates same function exported as default and named", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("deduplicates same function exported as default and named", () => + withTmp( + async (dir) => { const file = path.join(dir, "plugin.ts") const mark = path.join(dir, "count.txt") await Bun.write(mark, "") @@ -113,15 +133,17 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - expect(await fs.readFile(tmp.extra.mark, "utf8")).toBe("1") - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + expect(yield* Effect.promise(() => fs.readFile(tmp.extra.mark, "utf8"))).toBe("1") + }), + ), + ) - test("uses only default v1 server plugin when present", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("uses only default v1 server plugin when present", () => + withTmp( + async (dir) => { const file = path.join(dir, "plugin.ts") const mark = path.join(dir, "count.txt") await Bun.write( @@ -149,15 +171,17 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - expect(await Bun.file(tmp.extra.mark).text()).toBe("default") - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + expect(yield* Effect.promise(() => Bun.file(tmp.extra.mark).text())).toBe("default") + }), + ), + ) - test("rejects v1 file server plugin without id", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("rejects v1 file server plugin without id", () => + withTmp( + async (dir) => { const file = path.join(dir, "plugin.ts") const mark = path.join(dir, "called.txt") await Bun.write( @@ -180,20 +204,24 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - const called = await Bun.file(tmp.extra.mark) - .text() - .then(() => true) - .catch(() => false) - - expect(called).toBe(false) - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + const called = yield* Effect.promise(() => + Bun.file(tmp.extra.mark) + .text() + .then(() => true) + .catch(() => false), + ) + + expect(called).toBe(false) + }), + ), + ) - test("rejects v1 plugin that exports server and tui together", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("rejects v1 plugin that exports server and tui together", () => + withTmp( + async (dir) => { const file = path.join(dir, "plugin.ts") const mark = path.join(dir, "called.txt") await Bun.write( @@ -218,20 +246,24 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - const called = await Bun.file(tmp.extra.mark) - .text() - .then(() => true) - .catch(() => false) - - expect(called).toBe(false) - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + const called = yield* Effect.promise(() => + Bun.file(tmp.extra.mark) + .text() + .then(() => true) + .catch(() => false), + ) + + expect(called).toBe(false) + }), + ), + ) - test("resolves npm plugin specs with explicit and default versions", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("resolves npm plugin specs with explicit and default versions", () => + withTmp( + async (dir) => { const acme = path.join(dir, "node_modules", "acme-plugin") const scope = path.join(dir, "node_modules", "scope-plugin") await fs.mkdir(acme, { recursive: true }) @@ -254,26 +286,28 @@ describe("plugin.loader.shared", () => { return { acme, scope } }, - }) - - const add = spyOn(Npm, "add").mockImplementation(async (pkg) => { - if (pkg === "acme-plugin") return { directory: tmp.extra.acme, entrypoint: undefined } - return { directory: tmp.extra.scope, entrypoint: undefined } - }) - - try { - await load(tmp.path) - - expect(add.mock.calls).toContainEqual(["acme-plugin@latest"]) - expect(add.mock.calls).toContainEqual(["scope-plugin@2.3.4"]) - } finally { - add.mockRestore() - } - }) + (tmp) => + Effect.gen(function* () { + const add = spyOn(Npm, "add").mockImplementation(async (pkg) => { + if (pkg === "acme-plugin") return { directory: tmp.extra.acme, entrypoint: undefined } + return { directory: tmp.extra.scope, entrypoint: undefined } + }) + + try { + yield* load(tmp.path) + + expect(add.mock.calls).toContainEqual(["acme-plugin@latest"]) + expect(add.mock.calls).toContainEqual(["scope-plugin@2.3.4"]) + } finally { + add.mockRestore() + } + }), + ), + ) - test("loads npm server plugin from package ./server export", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("loads npm server plugin from package ./server export", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mods", "acme-plugin") const mark = path.join(dir, "server-called.txt") await fs.mkdir(mod, { recursive: true }) @@ -317,21 +351,23 @@ describe("plugin.loader.shared", () => { mark, } }, - }) - - const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) - - try { - await load(tmp.path) - expect(await Bun.file(tmp.extra.mark).text()).toBe("called") - } finally { - install.mockRestore() - } - }) + (tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) + + try { + yield* load(tmp.path) + expect(yield* Effect.promise(() => Bun.file(tmp.extra.mark).text())).toBe("called") + } finally { + install.mockRestore() + } + }), + ), + ) - test("loads npm server plugin from package server export without leading dot", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("loads npm server plugin from package server export without leading dot", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mods", "acme-plugin") const dist = path.join(mod, "dist") const mark = path.join(dir, "server-called.txt") @@ -374,21 +410,23 @@ describe("plugin.loader.shared", () => { mark, } }, - }) - - const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) - - try { - await load(tmp.path) - expect(await Bun.file(tmp.extra.mark).text()).toBe("called") - } finally { - install.mockRestore() - } - }) + (tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) + + try { + yield* load(tmp.path) + expect(yield* Effect.promise(() => Bun.file(tmp.extra.mark).text())).toBe("called") + } finally { + install.mockRestore() + } + }), + ), + ) - test("loads npm server plugin from package main without leading dot", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("loads npm server plugin from package main without leading dot", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mods", "acme-plugin") const dist = path.join(mod, "dist") const mark = path.join(dir, "main-called.txt") @@ -426,21 +464,23 @@ describe("plugin.loader.shared", () => { mark, } }, - }) - - const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) - - try { - await load(tmp.path) - expect(await Bun.file(tmp.extra.mark).text()).toBe("called") - } finally { - install.mockRestore() - } - }) + (tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) + + try { + yield* load(tmp.path) + expect(yield* Effect.promise(() => Bun.file(tmp.extra.mark).text())).toBe("called") + } finally { + install.mockRestore() + } + }), + ), + ) - test("does not use npm package exports dot for server entry", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("does not use npm package exports dot for server entry", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mods", "acme-plugin") const mark = path.join(dir, "dot-server.txt") await fs.mkdir(mod, { recursive: true }) @@ -471,26 +511,30 @@ describe("plugin.loader.shared", () => { return { mod, mark } }, - }) - - const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) - - try { - await load(tmp.path) - const called = await Bun.file(tmp.extra.mark) - .text() - .then(() => true) - .catch(() => false) - - expect(called).toBe(false) - } finally { - install.mockRestore() - } - }) + (tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) + + try { + yield* load(tmp.path) + const called = yield* Effect.promise(() => + Bun.file(tmp.extra.mark) + .text() + .then(() => true) + .catch(() => false), + ) + + expect(called).toBe(false) + } finally { + install.mockRestore() + } + }), + ), + ) - test("rejects npm server export that resolves outside plugin directory", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("rejects npm server export that resolves outside plugin directory", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mods", "acme-plugin") const outside = path.join(dir, "outside") const mark = path.join(dir, "outside-server.txt") @@ -534,25 +578,29 @@ describe("plugin.loader.shared", () => { mark, } }, - }) - - const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) - - try { - await load(tmp.path) - const called = await Bun.file(tmp.extra.mark) - .text() - .then(() => true) - .catch(() => false) - expect(called).toBe(false) - } finally { - install.mockRestore() - } - }) + (tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) + + try { + yield* load(tmp.path) + const called = yield* Effect.promise(() => + Bun.file(tmp.extra.mark) + .text() + .then(() => true) + .catch(() => false), + ) + expect(called).toBe(false) + } finally { + install.mockRestore() + } + }), + ), + ) - test("skips legacy codex and copilot auth plugin specs", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("skips legacy codex and copilot auth plugin specs", () => + withTmp( + async (dir) => { await Bun.write( path.join(dir, "opencode.json"), JSON.stringify( @@ -564,25 +612,27 @@ describe("plugin.loader.shared", () => { ), ) }, - }) - - const install = spyOn(Npm, "add").mockResolvedValue({ directory: "", entrypoint: undefined }) - - try { - await load(tmp.path) - - const pkgs = install.mock.calls.map((call) => call[0]) - expect(pkgs).toContain("regular-plugin@1.0.0") - expect(pkgs).not.toContain("opencode-openai-codex-auth@1.0.0") - expect(pkgs).not.toContain("opencode-copilot-auth@1.0.0") - } finally { - install.mockRestore() - } - }) + (_tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockResolvedValue({ directory: "", entrypoint: undefined }) + + try { + yield* load(_tmp.path) + + const pkgs = install.mock.calls.map((call) => call[0]) + expect(pkgs).toContain("regular-plugin@1.0.0") + expect(pkgs).not.toContain("opencode-openai-codex-auth@1.0.0") + expect(pkgs).not.toContain("opencode-copilot-auth@1.0.0") + } finally { + install.mockRestore() + } + }), + ), + ) - test("skips broken plugin when install fails", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("skips broken plugin when install fails", () => + withTmp( + async (dir) => { const ok = path.join(dir, "ok.ts") const mark = path.join(dir, "ok.txt") await Bun.write( @@ -604,22 +654,24 @@ describe("plugin.loader.shared", () => { ) return { mark } }, - }) - - const install = spyOn(Npm, "add").mockRejectedValue(new Error("boom")) - - try { - await load(tmp.path) - expect(install).toHaveBeenCalledWith("broken-plugin@9.9.9") - expect(await Bun.file(tmp.extra.mark).text()).toBe("ok") - } finally { - install.mockRestore() - } - }) + (tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockRejectedValue(new Error("boom")) + + try { + yield* load(tmp.path) + expect(install).toHaveBeenCalledWith("broken-plugin@9.9.9") + expect(yield* Effect.promise(() => Bun.file(tmp.extra.mark).text())).toBe("ok") + } finally { + install.mockRestore() + } + }), + ), + ) - test("continues loading plugins when plugin init throws", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("continues loading plugins when plugin init throws", () => + withTmp( + async (dir) => { const file = pathToFileURL(path.join(dir, "throws.ts")).href const ok = pathToFileURL(path.join(dir, "ok.ts")).href const mark = path.join(dir, "ok.txt") @@ -653,15 +705,17 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - expect(await Bun.file(tmp.extra.mark).text()).toBe("ok") - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + expect(yield* Effect.promise(() => Bun.file(tmp.extra.mark).text())).toBe("ok") + }), + ), + ) - test("continues loading plugins when plugin module has invalid export", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("continues loading plugins when plugin module has invalid export", () => + withTmp( + async (dir) => { const file = pathToFileURL(path.join(dir, "invalid.ts")).href const ok = pathToFileURL(path.join(dir, "ok.ts")).href const mark = path.join(dir, "ok.txt") @@ -687,15 +741,17 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - expect(await Bun.file(tmp.extra.mark).text()).toBe("ok") - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + expect(yield* Effect.promise(() => Bun.file(tmp.extra.mark).text())).toBe("ok") + }), + ), + ) - test("continues loading plugins when plugin import fails", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("continues loading plugins when plugin import fails", () => + withTmp( + async (dir) => { const missing = pathToFileURL(path.join(dir, "missing-plugin.ts")).href const ok = pathToFileURL(path.join(dir, "ok.ts")).href const mark = path.join(dir, "ok.txt") @@ -716,15 +772,17 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - expect(await Bun.file(tmp.extra.mark).text()).toBe("ok") - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + expect(yield* Effect.promise(() => Bun.file(tmp.extra.mark).text())).toBe("ok") + }), + ), + ) - test("loads object plugin via plugin.server", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("loads object plugin via plugin.server", () => + withTmp( + async (dir) => { const file = path.join(dir, "object-plugin.ts") const mark = path.join(dir, "object-called.txt") await Bun.write( @@ -749,15 +807,17 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - expect(await fs.readFile(tmp.extra.mark, "utf8")).toBe("called") - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + expect(yield* Effect.promise(() => fs.readFile(tmp.extra.mark, "utf8"))).toBe("called") + }), + ), + ) - test("passes tuple plugin options into server plugin", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("passes tuple plugin options into server plugin", () => + withTmp( + async (dir) => { const file = path.join(dir, "options-plugin.ts") const mark = path.join(dir, "options.json") await Bun.write( @@ -782,18 +842,20 @@ describe("plugin.loader.shared", () => { return { mark } }, - }) - - await load(tmp.path) - expect(await Filesystem.readJson<{ source: string; enabled: boolean }>(tmp.extra.mark)).toEqual({ - source: "tuple", - enabled: true, - }) - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + expect(yield* Effect.promise(() => Filesystem.readJson<{ source: string; enabled: boolean }>(tmp.extra.mark))).toEqual({ + source: "tuple", + enabled: true, + }) + }), + ), + ) - test("initializes server plugins in config order", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("initializes server plugins in config order", () => + withTmp( + async (dir) => { const a = path.join(dir, "a-plugin.ts") const b = path.join(dir, "b-plugin.ts") const marker = path.join(dir, "server-order.txt") @@ -833,16 +895,18 @@ export default { return { marker } }, - }) - - await load(tmp.path) - const lines = (await fs.readFile(tmp.extra.marker, "utf8")).trim().split("\n") - expect(lines).toEqual(["a-start", "a-end", "b"]) - }) + (tmp) => + Effect.gen(function* () { + yield* load(tmp.path) + const lines = (yield* Effect.promise(() => fs.readFile(tmp.extra.marker, "utf8"))).trim().split("\n") + expect(lines).toEqual(["a-start", "a-end", "b"]) + }), + ), + ) - test("skips external plugins in pure mode", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("skips external plugins in pure mode", () => + withTmp( + async (dir) => { const file = path.join(dir, "plugin.ts") const mark = path.join(dir, "called.txt") await Bun.write( @@ -866,30 +930,34 @@ export default { return { mark } }, - }) - - const pure = process.env.OPENCODE_PURE - process.env.OPENCODE_PURE = "1" - - try { - await load(tmp.path) - const called = await fs - .readFile(tmp.extra.mark, "utf8") - .then(() => true) - .catch(() => false) - expect(called).toBe(false) - } finally { - if (pure === undefined) { - delete process.env.OPENCODE_PURE - } else { - process.env.OPENCODE_PURE = pure - } - } - }) + (tmp) => + Effect.gen(function* () { + const pure = process.env.OPENCODE_PURE + process.env.OPENCODE_PURE = "1" + + try { + yield* load(tmp.path) + const called = yield* Effect.promise(() => + fs + .readFile(tmp.extra.mark, "utf8") + .then(() => true) + .catch(() => false), + ) + expect(called).toBe(false) + } finally { + if (pure === undefined) { + delete process.env.OPENCODE_PURE + } else { + process.env.OPENCODE_PURE = pure + } + } + }), + ), + ) - test("reads oc-themes from package manifest", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("reads oc-themes from package manifest", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mod") await fs.mkdir(path.join(mod, "themes"), { recursive: true }) await Bun.write( @@ -907,25 +975,27 @@ export default { return { mod } }, - }) - - const file = path.join(tmp.extra.mod, "package.json") - const json = await Filesystem.readJson>(file) - const list = readPackageThemes("acme-plugin", { - dir: tmp.extra.mod, - pkg: file, - json, - }) - - expect(list).toEqual([ - Filesystem.resolve(path.join(tmp.extra.mod, "themes", "one.json")), - Filesystem.resolve(path.join(tmp.extra.mod, "themes", "two.json")), - ]) - }) + (tmp) => + Effect.gen(function* () { + const file = path.join(tmp.extra.mod, "package.json") + const json = yield* Effect.promise(() => Filesystem.readJson>(file)) + const list = readPackageThemes("acme-plugin", { + dir: tmp.extra.mod, + pkg: file, + json, + }) + + expect(list).toEqual([ + Filesystem.resolve(path.join(tmp.extra.mod, "themes", "one.json")), + Filesystem.resolve(path.join(tmp.extra.mod, "themes", "two.json")), + ]) + }), + ), + ) - test("handles no-entrypoint tui packages via missing callback", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("handles no-entrypoint tui packages via missing callback", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mods", "acme-plugin") await fs.mkdir(path.join(mod, "themes"), { recursive: true }) await Bun.write( @@ -943,54 +1013,58 @@ export default { await Bun.write(path.join(mod, "themes", "night.json"), "{}\n") return { mod } }, - }) - - const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) - const missing: string[] = [] - - try { - const loaded = await PluginLoader.loadExternal({ - items: [ - { - spec: "acme-plugin@1.0.0", - scope: "local" as const, - source: tmp.path, - }, - ], - kind: "tui", - missing: async (item) => { - if (!item.pkg) return - const themes = readPackageThemes(item.spec, item.pkg) - if (!themes.length) return - return { - spec: item.spec, - target: item.target, - themes, + (tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) + const missing: string[] = [] + + try { + const loaded = yield* Effect.promise(() => + PluginLoader.loadExternal({ + items: [ + { + spec: "acme-plugin@1.0.0", + scope: "local" as const, + source: tmp.path, + }, + ], + kind: "tui", + missing: async (item) => { + if (!item.pkg) return + const themes = readPackageThemes(item.spec, item.pkg) + if (!themes.length) return + return { + spec: item.spec, + target: item.target, + themes, + } + }, + report: { + missing(_candidate, _retry, message) { + missing.push(message) + }, + }, + }), + ) + + expect(loaded).toEqual([ + { + spec: "acme-plugin@1.0.0", + target: tmp.extra.mod, + themes: [Filesystem.resolve(path.join(tmp.extra.mod, "themes", "night.json"))], + }, + ]) + expect(missing).toHaveLength(0) + } finally { + install.mockRestore() } - }, - report: { - missing(_candidate, _retry, message) { - missing.push(message) - }, - }, - }) - - expect(loaded).toEqual([ - { - spec: "acme-plugin@1.0.0", - target: tmp.extra.mod, - themes: [Filesystem.resolve(path.join(tmp.extra.mod, "themes", "night.json"))], - }, - ]) - expect(missing).toHaveLength(0) - } finally { - install.mockRestore() - } - }) + }), + ), + ) - test("passes package metadata for entrypoint tui plugins", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("passes package metadata for entrypoint tui plugins", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mods", "acme-plugin") await fs.mkdir(path.join(mod, "themes"), { recursive: true }) await Bun.write( @@ -1012,64 +1086,70 @@ export default { await Bun.write(path.join(mod, "themes", "night.json"), "{}\n") return { mod } }, - }) - - const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) - - try { - const loaded = await PluginLoader.loadExternal({ - items: [ - { - spec: "acme-plugin@1.0.0", - scope: "local" as const, - source: tmp.path, - }, - ], - kind: "tui", - finish: async (item) => { - if (!item.pkg) return - return { - spec: item.spec, - themes: readPackageThemes(item.spec, item.pkg), + (tmp) => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined }) + + try { + const loaded = yield* Effect.promise(() => + PluginLoader.loadExternal({ + items: [ + { + spec: "acme-plugin@1.0.0", + scope: "local" as const, + source: tmp.path, + }, + ], + kind: "tui", + finish: async (item) => { + if (!item.pkg) return + return { + spec: item.spec, + themes: readPackageThemes(item.spec, item.pkg), + } + }, + }), + ) + + expect(loaded).toEqual([ + { + spec: "acme-plugin@1.0.0", + themes: [Filesystem.resolve(path.join(tmp.extra.mod, "themes", "night.json"))], + }, + ]) + } finally { + install.mockRestore() } - }, - }) - - expect(loaded).toEqual([ - { - spec: "acme-plugin@1.0.0", - themes: [Filesystem.resolve(path.join(tmp.extra.mod, "themes", "night.json"))], - }, - ]) - } finally { - install.mockRestore() - } - }) + }), + ), + ) - test("rejects oc-themes path traversal", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("rejects oc-themes path traversal", () => + withTmp( + async (dir) => { const mod = path.join(dir, "mod") await fs.mkdir(mod, { recursive: true }) const file = path.join(mod, "package.json") await Bun.write(file, JSON.stringify({ name: "acme", "oc-themes": ["../escape.json"] }, null, 2)) return { mod, file } }, - }) - - const json = await Filesystem.readJson>(tmp.extra.file) - expect(() => - readPackageThemes("acme", { - dir: tmp.extra.mod, - pkg: tmp.extra.file, - json, - }), - ).toThrow("outside plugin directory") - }) + (tmp) => + Effect.gen(function* () { + const json = yield* Effect.promise(() => Filesystem.readJson>(tmp.extra.file)) + expect(() => + readPackageThemes("acme", { + dir: tmp.extra.mod, + pkg: tmp.extra.file, + json, + }), + ).toThrow("outside plugin directory") + }), + ), + ) - test("retries failed file plugins once after wait and keeps order", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("retries failed file plugins once after wait and keeps order", () => + withTmp( + async (dir) => { const a = path.join(dir, "a") const b = path.join(dir, "b") const aSpec = pathToFileURL(a).href @@ -1078,110 +1158,122 @@ export default { await fs.mkdir(b, { recursive: true }) return { a, b, aSpec, bSpec } }, - }) - - let wait = 0 - const calls: Array<[string, boolean]> = [] - - const loaded = await PluginLoader.loadExternal({ - items: [tmp.extra.aSpec, tmp.extra.bSpec].map((spec) => ({ - spec, - scope: "local" as const, - source: tmp.path, - })), - kind: "tui", - wait: async () => { - wait += 1 - await Bun.write(path.join(tmp.extra.a, "index.ts"), "export default {}\n") - await Bun.write(path.join(tmp.extra.b, "index.ts"), "export default {}\n") - }, - report: { - start(candidate, retry) { - calls.push([candidate.plan.spec, retry]) - }, - }, - }) - - expect(wait).toBe(1) - expect(calls).toEqual([ - [tmp.extra.aSpec, false], - [tmp.extra.bSpec, false], - [tmp.extra.aSpec, true], - [tmp.extra.bSpec, true], - ]) - expect(loaded.map((item) => item.spec)).toEqual([tmp.extra.aSpec, tmp.extra.bSpec]) - }) + (tmp) => + Effect.gen(function* () { + let wait = 0 + const calls: Array<[string, boolean]> = [] + + const loaded = yield* Effect.promise(() => + PluginLoader.loadExternal({ + items: [tmp.extra.aSpec, tmp.extra.bSpec].map((spec) => ({ + spec, + scope: "local" as const, + source: tmp.path, + })), + kind: "tui", + wait: async () => { + wait += 1 + await Bun.write(path.join(tmp.extra.a, "index.ts"), "export default {}\n") + await Bun.write(path.join(tmp.extra.b, "index.ts"), "export default {}\n") + }, + report: { + start(candidate, retry) { + calls.push([candidate.plan.spec, retry]) + }, + }, + }), + ) + + expect(wait).toBe(1) + expect(calls).toEqual([ + [tmp.extra.aSpec, false], + [tmp.extra.bSpec, false], + [tmp.extra.aSpec, true], + [tmp.extra.bSpec, true], + ]) + expect(loaded.map((item) => item.spec)).toEqual([tmp.extra.aSpec, tmp.extra.bSpec]) + }), + ), + ) - test("retries file plugins when finish returns undefined", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { + it.live("retries file plugins when finish returns undefined", () => + withTmp( + async (dir) => { const file = path.join(dir, "plugin.ts") const spec = pathToFileURL(file).href await Bun.write(file, "export default {}\n") return { spec } }, - }) - - let wait = 0 - let count = 0 - - const loaded = await PluginLoader.loadExternal({ - items: [ - { - spec: tmp.extra.spec, - scope: "local" as const, - source: tmp.path, - }, - ], - kind: "tui", - wait: async () => { - wait += 1 - }, - finish: async (load, _item, retry) => { - count += 1 - if (!retry) return - return { - retry, - spec: load.spec, - } - }, - }) + (tmp) => + Effect.gen(function* () { + let wait = 0 + let count = 0 + + const loaded = yield* Effect.promise(() => + PluginLoader.loadExternal({ + items: [ + { + spec: tmp.extra.spec, + scope: "local" as const, + source: tmp.path, + }, + ], + kind: "tui", + wait: async () => { + wait += 1 + }, + finish: async (load, _item, retry) => { + count += 1 + if (!retry) return + return { + retry, + spec: load.spec, + } + }, + }), + ) - expect(wait).toBe(1) - expect(count).toBe(2) - expect(loaded).toEqual([{ retry: true, spec: tmp.extra.spec }]) - }) + expect(wait).toBe(1) + expect(count).toBe(2) + expect(loaded).toEqual([{ retry: true, spec: tmp.extra.spec }]) + }), + ), + ) - test("does not wait or retry npm plugin failures", async () => { - const install = spyOn(Npm, "add").mockRejectedValue(new Error("boom")) - let wait = 0 - const errors: Array<[string, boolean]> = [] - - try { - const loaded = await PluginLoader.loadExternal({ - items: [ - { - spec: "acme-plugin@1.0.0", - scope: "local" as const, - source: "test", - }, - ], - kind: "tui", - wait: async () => { - wait += 1 - }, - report: { - error(_candidate, retry, stage) { - errors.push([stage, retry]) - }, - }, - }) - - expect(loaded).toEqual([]) - expect(wait).toBe(0) - expect(errors).toEqual([["install", false]]) - } finally { - install.mockRestore() - } - }) + it.live("does not wait or retry npm plugin failures", () => + Effect.gen(function* () { + const install = spyOn(Npm, "add").mockRejectedValue(new Error("boom")) + let wait = 0 + const errors: Array<[string, boolean]> = [] + + try { + const loaded = yield* Effect.promise(() => + PluginLoader.loadExternal({ + items: [ + { + spec: "acme-plugin@1.0.0", + scope: "local" as const, + source: "test", + }, + ], + kind: "tui", + wait: async () => { + wait += 1 + }, + report: { + error(_candidate, retry, stage) { + errors.push([stage, retry]) + }, + }, + }), + ) + + expect(loaded).toEqual([]) + expect(wait).toBe(0) + expect(errors).toEqual([["install", false]]) + } finally { + install.mockRestore() + } + }), + ) }) From 1d4613006aa2dc25ec2a8050c87763b74f27c46d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 15:41:46 -0400 Subject: [PATCH 131/378] test(project): migrate instance tests to Effect runner (#27130) --- .../opencode/test/project/instance.test.ts | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/opencode/test/project/instance.test.ts b/packages/opencode/test/project/instance.test.ts index 99b0f0666..5ec64754d 100644 --- a/packages/opencode/test/project/instance.test.ts +++ b/packages/opencode/test/project/instance.test.ts @@ -1,13 +1,12 @@ import { afterEach, describe, expect } from "bun:test" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { Effect, Fiber, Layer } from "effect" +import { Deferred, Effect, Fiber, Layer } from "effect" import { InstanceRef } from "../../src/effect/instance-ref" import { registerDisposer } from "../../src/effect/instance-registry" import { InstanceBootstrap } from "../../src/project/bootstrap-service" import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { InstanceStore } from "../../src/project/instance-store" -import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture" +import { disposeAllInstances, TestInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" let bootstrapRun: Effect.Effect = Effect.void @@ -75,18 +74,18 @@ describe("InstanceStore", () => { Effect.gen(function* () { const dir = yield* tmpdirScoped({ git: true }) const store = yield* InstanceStore.Service - const started = Promise.withResolvers() - const release = Promise.withResolvers() + const started = yield* Deferred.make() + const release = yield* Deferred.make() let initialized = 0 - bootstrapRun = Effect.promise(async () => { + bootstrapRun = Effect.gen(function* () { initialized++ - started.resolve() - await release.promise + yield* Deferred.succeed(started, undefined) + yield* Deferred.await(release) }) const first = yield* store.load({ directory: dir }).pipe(Effect.forkScoped) - yield* Effect.promise(() => started.promise) + yield* Deferred.await(started) bootstrapRun = Effect.sync(() => { initialized++ @@ -94,7 +93,7 @@ describe("InstanceStore", () => { const second = yield* store.load({ directory: dir }).pipe(Effect.forkScoped) expect(initialized).toBe(1) - release.resolve() + yield* Deferred.succeed(release, undefined) const [firstCtx, secondCtx] = yield* Effect.all([Fiber.join(first), Fiber.join(second)]) expect(secondCtx).toBe(firstCtx) @@ -147,8 +146,8 @@ describe("InstanceStore", () => { Effect.gen(function* () { const dir = yield* tmpdirScoped({ git: true }) const store = yield* InstanceStore.Service - const reloading = Promise.withResolvers() - const releaseReload = Promise.withResolvers() + const reloading = yield* Deferred.make() + const releaseReload = yield* Deferred.make() const disposed: Array = [] const off = registerDisposer(async (directory) => { disposed.push(directory) @@ -156,15 +155,15 @@ describe("InstanceStore", () => { yield* Effect.addFinalizer(() => Effect.sync(off)) const first = yield* store.load({ directory: dir }) - bootstrapRun = Effect.promise(async () => { - reloading.resolve() - await releaseReload.promise + bootstrapRun = Effect.gen(function* () { + yield* Deferred.succeed(reloading, undefined) + yield* Deferred.await(releaseReload) }) const reload = yield* store.reload({ directory: dir }).pipe(Effect.forkScoped) - yield* Effect.promise(() => reloading.promise) + yield* Deferred.await(reloading) const staleDispose = yield* store.dispose(first).pipe(Effect.forkScoped) - releaseReload.resolve() + yield* Deferred.succeed(releaseReload, undefined) const second = yield* Fiber.join(reload) yield* Fiber.join(staleDispose) @@ -178,23 +177,23 @@ describe("InstanceStore", () => { Effect.gen(function* () { const dir = yield* tmpdirScoped({ git: true }) const store = yield* InstanceStore.Service - const disposing = Promise.withResolvers() - const releaseDispose = Promise.withResolvers() + const disposing = yield* Deferred.make() + const releaseDispose = yield* Deferred.make() const disposed: Array = [] const off = registerDisposer(async (directory) => { disposed.push(directory) - disposing.resolve() - await releaseDispose.promise + Deferred.doneUnsafe(disposing, Effect.void) + await Effect.runPromise(Deferred.await(releaseDispose)) }) yield* Effect.addFinalizer(() => Effect.sync(off)) yield* store.load({ directory: dir }) const first = yield* store.disposeAll().pipe(Effect.forkScoped) - yield* Effect.promise(() => disposing.promise) + yield* Deferred.await(disposing) const second = yield* store.disposeAll().pipe(Effect.forkScoped) expect(disposed).toEqual([dir]) - releaseDispose.resolve() + yield* Deferred.succeed(releaseDispose, undefined) yield* Effect.all([Fiber.join(first), Fiber.join(second)]) expect(disposed).toEqual([dir]) }), @@ -221,19 +220,17 @@ describe("InstanceStore", () => { }), ) - it.live("provides legacy Promise callers with instance ALS", () => + it.instance("provides legacy Promise callers with instance ALS", () => Effect.gen(function* () { - const dir = yield* tmpdirScoped({ git: true }) + const test = yield* TestInstance + const ctx = yield* InstanceRef + if (!ctx) throw new Error("InstanceRef not provided") - const directory = yield* Effect.promise(() => - WithInstance.provide({ - directory: dir, - fn: () => Instance.directory, - }), - ) + const directory = yield* Effect.promise(() => Promise.resolve(Instance.restore(ctx, () => Instance.directory))) - expect(directory).toBe(dir) + expect(directory).toBe(test.directory) expect(() => Instance.current).toThrow() }), + { git: true }, ) }) From e46ab34d27ccbe7afe108331d6bfed59dcc6a14e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 19:44:26 +0000 Subject: [PATCH 132/378] chore: generate --- packages/opencode/src/acp/agent.ts | 3 ++- .../test/plugin/loader-shared.test.ts | 4 +++- .../opencode/test/project/instance.test.ts | 24 ++++++++++--------- .../opencode/test/skill/discovery.test.ts | 4 +++- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index a8eacf835..aa123d599 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -1328,7 +1328,8 @@ export class Agent implements ACPAgent { if (!current) { this.sessionManager.setModel(session.id, model) } - const agent = session.modeId ?? (await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultInfo()))).name + const agent = + session.modeId ?? (await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultInfo()))).name const parts: Array< | { type: "text"; text: string; synthetic?: boolean; ignored?: boolean } diff --git a/packages/opencode/test/plugin/loader-shared.test.ts b/packages/opencode/test/plugin/loader-shared.test.ts index ffdb3291b..1b6372390 100644 --- a/packages/opencode/test/plugin/loader-shared.test.ts +++ b/packages/opencode/test/plugin/loader-shared.test.ts @@ -845,7 +845,9 @@ describe("plugin.loader.shared", () => { (tmp) => Effect.gen(function* () { yield* load(tmp.path) - expect(yield* Effect.promise(() => Filesystem.readJson<{ source: string; enabled: boolean }>(tmp.extra.mark))).toEqual({ + expect( + yield* Effect.promise(() => Filesystem.readJson<{ source: string; enabled: boolean }>(tmp.extra.mark)), + ).toEqual({ source: "tuple", enabled: true, }) diff --git a/packages/opencode/test/project/instance.test.ts b/packages/opencode/test/project/instance.test.ts index 5ec64754d..dc87fde45 100644 --- a/packages/opencode/test/project/instance.test.ts +++ b/packages/opencode/test/project/instance.test.ts @@ -220,17 +220,19 @@ describe("InstanceStore", () => { }), ) - it.instance("provides legacy Promise callers with instance ALS", () => - Effect.gen(function* () { - const test = yield* TestInstance - const ctx = yield* InstanceRef - if (!ctx) throw new Error("InstanceRef not provided") - - const directory = yield* Effect.promise(() => Promise.resolve(Instance.restore(ctx, () => Instance.directory))) - - expect(directory).toBe(test.directory) - expect(() => Instance.current).toThrow() - }), + it.instance( + "provides legacy Promise callers with instance ALS", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceRef + if (!ctx) throw new Error("InstanceRef not provided") + + const directory = yield* Effect.promise(() => Promise.resolve(Instance.restore(ctx, () => Instance.directory))) + + expect(directory).toBe(test.directory) + expect(() => Instance.current).toThrow() + }), { git: true }, ) }) diff --git a/packages/opencode/test/skill/discovery.test.ts b/packages/opencode/test/skill/discovery.test.ts index 43018d9a4..0b07d4df0 100644 --- a/packages/opencode/test/skill/discovery.test.ts +++ b/packages/opencode/test/skill/discovery.test.ts @@ -101,7 +101,9 @@ describe("Discovery.pull", () => { const refs = path.join(agentsSdk, "references") expect(yield* Effect.promise(() => Filesystem.exists(path.join(agentsSdk, "SKILL.md")))).toBe(true) // agents-sdk has reference files per the index - const refDir = yield* Effect.promise(() => Array.fromAsync(new Bun.Glob("**/*.md").scan({ cwd: refs, onlyFiles: true }))) + const refDir = yield* Effect.promise(() => + Array.fromAsync(new Bun.Glob("**/*.md").scan({ cwd: refs, onlyFiles: true })), + ) expect(refDir.length).toBeGreaterThan(0) } }), From c5849e56cc5280427be59c2252429b6c9938951b Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 15:47:17 -0400 Subject: [PATCH 133/378] test(project): migrate project tests to Effect runner (#27134) --- .../opencode/test/project/project.test.ts | 812 ++++++++++-------- 1 file changed, 432 insertions(+), 380 deletions(-) diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 9906b3164..9ed1e60bb 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -4,26 +4,41 @@ import { Project } from "@/project/project" import * as Log from "@opencode-ai/core/util/log" import { $ } from "bun" import path from "path" -import { tmpdir } from "../fixture/fixture" +import { tmpdirScoped } from "../fixture/fixture" import { GlobalBus } from "../../src/bus/global" import { ProjectID } from "../../src/project/schema" -import { Effect, Layer, Stream } from "effect" +import { Cause, Effect, Exit, Layer, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { testEffect } from "../lib/effect" void Log.init({ print: false }) const encoder = new TextEncoder() -function run(fn: (svc: Project.Interface) => Effect.Effect, layer = Project.defaultLayer) { - return Effect.runPromise( - Effect.gen(function* () { - const svc = yield* Project.Service - return yield* fn(svc) - }).pipe(Effect.provide(layer)), - ) +const layer = Layer.mergeAll(Project.defaultLayer, CrossSpawnSpawner.defaultLayer) +const it = testEffect(layer) + +function run(fn: (svc: Project.Interface) => Effect.Effect) { + return Effect.gen(function* () { + const svc = yield* Project.Service + return yield* fn(svc) + }) +} + +function gitTmpdir() { + return Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* Effect.promise(() => $`git init`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git config core.fsmonitor false`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git config commit.gpgsign false`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git config user.email "test@opencode.test"`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git config user.name "Test"`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git commit --allow-empty -m ${`root commit ${tmp}`}`.cwd(tmp).quiet()) + return tmp + }) } /** @@ -70,400 +85,430 @@ function projectLayerWithFailure(failArg: string) { ) } +const failureIt = (failArg: string) => testEffect(Layer.mergeAll(projectLayerWithFailure(failArg), CrossSpawnSpawner.defaultLayer)) + describe("Project.fromDirectory", () => { - test("should handle git repository with no commits", async () => { - await using tmp = await tmpdir() - await $`git init`.cwd(tmp.path).quiet() + it.live("should handle git repository with no commits", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* Effect.promise(() => $`git init`.cwd(tmp).quiet()) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - expect(project).toBeDefined() - expect(project.id).toBe(ProjectID.global) - expect(project.vcs).toBe("git") - expect(project.worktree).toBe(tmp.path) + expect(project).toBeDefined() + expect(project.id).toBe(ProjectID.global) + expect(project.vcs).toBe("git") + expect(project.worktree).toBe(tmp) - const opencodeFile = path.join(tmp.path, ".git", "opencode") - expect(await Bun.file(opencodeFile).exists()).toBe(false) - }) + const opencodeFile = path.join(tmp, ".git", "opencode") + expect(yield* Effect.promise(() => Bun.file(opencodeFile).exists())).toBe(false) + }), + ) - test("should handle git repository with commits", async () => { - await using tmp = await tmpdir({ git: true }) + it.live("should handle git repository with commits", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - expect(project).toBeDefined() - expect(project.id).not.toBe(ProjectID.global) - expect(project.vcs).toBe("git") - expect(project.worktree).toBe(tmp.path) + expect(project).toBeDefined() + expect(project.id).not.toBe(ProjectID.global) + expect(project.vcs).toBe("git") + expect(project.worktree).toBe(tmp) - const opencodeFile = path.join(tmp.path, ".git", "opencode") - expect(await Bun.file(opencodeFile).exists()).toBe(true) - }) + const opencodeFile = path.join(tmp, ".git", "opencode") + expect(yield* Effect.promise(() => Bun.file(opencodeFile).exists())).toBe(true) + }), + ) - test("returns global for non-git directory", async () => { - await using tmp = await tmpdir() - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) - expect(project.id).toBe(ProjectID.global) - }) + it.live("returns global for non-git directory", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) + expect(project.id).toBe(ProjectID.global) + }), + ) - test("derives stable project ID from root commit", async () => { - await using tmp = await tmpdir({ git: true }) - const { project: a } = await run((svc) => svc.fromDirectory(tmp.path)) - const { project: b } = await run((svc) => svc.fromDirectory(tmp.path)) - expect(b.id).toBe(a.id) - }) + it.live("derives stable project ID from root commit", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project: a } = yield* run((svc) => svc.fromDirectory(tmp)) + const { project: b } = yield* run((svc) => svc.fromDirectory(tmp)) + expect(b.id).toBe(a.id) + }), + ) }) describe("Project.fromDirectory git failure paths", () => { - test("keeps vcs when rev-list exits non-zero (no commits)", async () => { - await using tmp = await tmpdir() - await $`git init`.cwd(tmp.path).quiet() - - // rev-list fails because HEAD doesn't exist yet — this is the natural scenario - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) - expect(project.vcs).toBe("git") - expect(project.id).toBe(ProjectID.global) - expect(project.worktree).toBe(tmp.path) - }) + it.live("keeps vcs when rev-list exits non-zero (no commits)", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped() + yield* Effect.promise(() => $`git init`.cwd(tmp).quiet()) + + // rev-list fails because HEAD doesn't exist yet: this is the natural scenario. + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) + expect(project.vcs).toBe("git") + expect(project.id).toBe(ProjectID.global) + expect(project.worktree).toBe(tmp) + }), + ) - test("handles show-toplevel failure gracefully", async () => { - await using tmp = await tmpdir({ git: true }) - const layer = projectLayerWithFailure("--show-toplevel") + failureIt("--show-toplevel").live("handles show-toplevel failure gracefully", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() - const { project, sandbox } = await run((svc) => svc.fromDirectory(tmp.path), layer) - expect(project.worktree).toBe(tmp.path) - expect(sandbox).toBe(tmp.path) - }) + const { project, sandbox } = yield* run((svc) => svc.fromDirectory(tmp)) + expect(project.worktree).toBe(tmp) + expect(sandbox).toBe(tmp) + }), + ) - test("handles git-common-dir failure gracefully", async () => { - await using tmp = await tmpdir({ git: true }) - const layer = projectLayerWithFailure("--git-common-dir") + failureIt("--git-common-dir").live("handles git-common-dir failure gracefully", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() - const { project, sandbox } = await run((svc) => svc.fromDirectory(tmp.path), layer) - expect(project.worktree).toBe(tmp.path) - expect(sandbox).toBe(tmp.path) - }) + const { project, sandbox } = yield* run((svc) => svc.fromDirectory(tmp)) + expect(project.worktree).toBe(tmp) + expect(sandbox).toBe(tmp) + }), + ) }) describe("Project.fromDirectory with worktrees", () => { - test("should set worktree to root when called from root", async () => { - await using tmp = await tmpdir({ git: true }) + it.live("should set worktree to root when called from root", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() - const { project, sandbox } = await run((svc) => svc.fromDirectory(tmp.path)) + const { project, sandbox } = yield* run((svc) => svc.fromDirectory(tmp)) - expect(project.worktree).toBe(tmp.path) - expect(sandbox).toBe(tmp.path) - expect(project.sandboxes).not.toContain(tmp.path) - }) + expect(project.worktree).toBe(tmp) + expect(sandbox).toBe(tmp) + expect(project.sandboxes).not.toContain(tmp) + }), + ) - test("should set worktree to root when called from a worktree", async () => { - await using tmp = await tmpdir({ git: true }) + it.live("should set worktree to root when called from a worktree", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() - const worktreePath = path.join(tmp.path, "..", path.basename(tmp.path) + "-worktree") - try { - await $`git worktree add ${worktreePath} -b test-branch-${Date.now()}`.cwd(tmp.path).quiet() + const worktreePath = path.join(tmp, "..", path.basename(tmp) + "-worktree") + yield* Effect.addFinalizer(() => Effect.promise(() => $`git worktree remove ${worktreePath}`.cwd(tmp).quiet().catch(() => {}))) + yield* Effect.promise(() => $`git worktree add ${worktreePath} -b test-branch-${Date.now()}`.cwd(tmp).quiet()) - const { project, sandbox } = await run((svc) => svc.fromDirectory(worktreePath)) + const { project, sandbox } = yield* run((svc) => svc.fromDirectory(worktreePath)) - expect(project.worktree).toBe(tmp.path) + expect(project.worktree).toBe(tmp) expect(sandbox).toBe(worktreePath) expect(project.sandboxes).toContain(worktreePath) - expect(project.sandboxes).not.toContain(tmp.path) - } finally { - await $`git worktree remove ${worktreePath}` - .cwd(tmp.path) - .quiet() - .catch(() => {}) - } - }) + expect(project.sandboxes).not.toContain(tmp) + }), + ) - test("worktree should share project ID with main repo", async () => { - await using tmp = await tmpdir({ git: true }) + it.live("worktree should share project ID with main repo", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() - const { project: main } = await run((svc) => svc.fromDirectory(tmp.path)) + const { project: main } = yield* run((svc) => svc.fromDirectory(tmp)) - const worktreePath = path.join(tmp.path, "..", path.basename(tmp.path) + "-wt-shared") - try { - await $`git worktree add ${worktreePath} -b shared-${Date.now()}`.cwd(tmp.path).quiet() + const worktreePath = path.join(tmp, "..", path.basename(tmp) + "-wt-shared") + yield* Effect.addFinalizer(() => Effect.promise(() => $`git worktree remove ${worktreePath}`.cwd(tmp).quiet().catch(() => {}))) + yield* Effect.promise(() => $`git worktree add ${worktreePath} -b shared-${Date.now()}`.cwd(tmp).quiet()) - const { project: wt } = await run((svc) => svc.fromDirectory(worktreePath)) + const { project: wt } = yield* run((svc) => svc.fromDirectory(worktreePath)) expect(wt.id).toBe(main.id) // Cache should live in the common .git dir, not the worktree's .git file - const cache = path.join(tmp.path, ".git", "opencode") - const exists = await Bun.file(cache).exists() + const cache = path.join(tmp, ".git", "opencode") + const exists = yield* Effect.promise(() => Bun.file(cache).exists()) expect(exists).toBe(true) - } finally { - await $`git worktree remove ${worktreePath}` - .cwd(tmp.path) - .quiet() - .catch(() => {}) - } - }) + }), + ) - test("separate clones of the same repo should share project ID", async () => { - await using tmp = await tmpdir({ git: true }) + it.live("separate clones of the same repo should share project ID", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() // Create a bare remote, push, then clone into a second directory - const bare = tmp.path + "-bare" - const clone = tmp.path + "-clone" - try { - await $`git clone --bare ${tmp.path} ${bare}`.quiet() - await $`git clone ${bare} ${clone}`.quiet() + const bare = tmp + "-bare" + const clone = tmp + "-clone" + yield* Effect.addFinalizer(() => Effect.promise(() => $`rm -rf ${bare} ${clone}`.quiet().nothrow()).pipe(Effect.ignore)) + yield* Effect.promise(() => $`git clone --bare ${tmp} ${bare}`.quiet()) + yield* Effect.promise(() => $`git clone ${bare} ${clone}`.quiet()) - const { project: a } = await run((svc) => svc.fromDirectory(tmp.path)) - const { project: b } = await run((svc) => svc.fromDirectory(clone)) + const { project: a } = yield* run((svc) => svc.fromDirectory(tmp)) + const { project: b } = yield* run((svc) => svc.fromDirectory(clone)) expect(b.id).toBe(a.id) - } finally { - await $`rm -rf ${bare} ${clone}`.quiet().nothrow() - } - }) - - test("should accumulate multiple worktrees in sandboxes", async () => { - await using tmp = await tmpdir({ git: true }) + }), + ) - const worktree1 = path.join(tmp.path, "..", path.basename(tmp.path) + "-wt1") - const worktree2 = path.join(tmp.path, "..", path.basename(tmp.path) + "-wt2") - try { - await $`git worktree add ${worktree1} -b branch-${Date.now()}`.cwd(tmp.path).quiet() - await $`git worktree add ${worktree2} -b branch-${Date.now() + 1}`.cwd(tmp.path).quiet() + it.live("should accumulate multiple worktrees in sandboxes", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + + const worktree1 = path.join(tmp, "..", path.basename(tmp) + "-wt1") + const worktree2 = path.join(tmp, "..", path.basename(tmp) + "-wt2") + yield* Effect.addFinalizer(() => + Effect.gen(function* () { + yield* Effect.promise(() => $`git worktree remove ${worktree1}`.cwd(tmp).quiet().catch(() => {})) + yield* Effect.promise(() => $`git worktree remove ${worktree2}`.cwd(tmp).quiet().catch(() => {})) + }), + ) + yield* Effect.promise(() => $`git worktree add ${worktree1} -b branch-${Date.now()}`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git worktree add ${worktree2} -b branch-${Date.now() + 1}`.cwd(tmp).quiet()) - await run((svc) => svc.fromDirectory(worktree1)) - const { project } = await run((svc) => svc.fromDirectory(worktree2)) + yield* run((svc) => svc.fromDirectory(worktree1)) + const { project } = yield* run((svc) => svc.fromDirectory(worktree2)) - expect(project.worktree).toBe(tmp.path) + expect(project.worktree).toBe(tmp) expect(project.sandboxes).toContain(worktree1) expect(project.sandboxes).toContain(worktree2) - expect(project.sandboxes).not.toContain(tmp.path) - } finally { - await $`git worktree remove ${worktree1}` - .cwd(tmp.path) - .quiet() - .catch(() => {}) - await $`git worktree remove ${worktree2}` - .cwd(tmp.path) - .quiet() - .catch(() => {}) - } - }) + expect(project.sandboxes).not.toContain(tmp) + }), + ) }) describe("Project.discover", () => { - test("should discover favicon.png in root", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should discover favicon.png in root", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) - await Bun.write(path.join(tmp.path, "favicon.png"), pngData) + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) + yield* Effect.promise(() => Bun.write(path.join(tmp, "favicon.png"), pngData)) - await run((svc) => svc.discover(project)) + yield* run((svc) => svc.discover(project)) - const updated = Project.get(project.id) - expect(updated).toBeDefined() - expect(updated!.icon).toBeDefined() - expect(updated!.icon?.url).toStartWith("data:") - expect(updated!.icon?.url).toContain("base64") - expect(updated!.icon?.color).toBeUndefined() - }) + const updated = Project.get(project.id) + expect(updated).toBeDefined() + expect(updated!.icon).toBeDefined() + expect(updated!.icon?.url).toStartWith("data:") + expect(updated!.icon?.url).toContain("base64") + expect(updated!.icon?.color).toBeUndefined() + }), + ) - test("should not discover non-image files", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should not discover non-image files", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - await Bun.write(path.join(tmp.path, "favicon.txt"), "not an image") + yield* Effect.promise(() => Bun.write(path.join(tmp, "favicon.txt"), "not an image")) - await run((svc) => svc.discover(project)) + yield* run((svc) => svc.discover(project)) - const updated = Project.get(project.id) - expect(updated).toBeDefined() - expect(updated!.icon).toBeUndefined() - }) + const updated = Project.get(project.id) + expect(updated).toBeDefined() + expect(updated!.icon).toBeUndefined() + }), + ) - test("should not discover favicon when override is set", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should not discover favicon when override is set", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - await run((svc) => - svc.update({ - projectID: project.id, - icon: { override: "data:image/png;base64,override" }, - }), - ) + yield* run((svc) => + svc.update({ + projectID: project.id, + icon: { override: "data:image/png;base64,override" }, + }), + ) - const updatedProject = await run((svc) => svc.get(project.id)) - if (!updatedProject) throw new Error("Project not found") + const updatedProject = yield* run((svc) => svc.get(project.id)) + if (!updatedProject) throw new Error("Project not found") - const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) - await Bun.write(path.join(tmp.path, "favicon.png"), pngData) + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) + yield* Effect.promise(() => Bun.write(path.join(tmp, "favicon.png"), pngData)) - await run((svc) => svc.discover(updatedProject)) + yield* run((svc) => svc.discover(updatedProject)) - const updated = Project.get(project.id) - expect(updated).toBeDefined() - expect(updated!.icon?.override).toBe("data:image/png;base64,override") - expect(updated!.icon?.url).toBeUndefined() - }) + const updated = Project.get(project.id) + expect(updated).toBeDefined() + expect(updated!.icon?.override).toBe("data:image/png;base64,override") + expect(updated!.icon?.url).toBeUndefined() + }), + ) }) describe("Project.update", () => { - test("should update name", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should update name", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - const updated = await run((svc) => - svc.update({ - projectID: project.id, - name: "New Project Name", - }), - ) + const updated = yield* run((svc) => + svc.update({ + projectID: project.id, + name: "New Project Name", + }), + ) - expect(updated.name).toBe("New Project Name") + expect(updated.name).toBe("New Project Name") - const fromDb = Project.get(project.id) - expect(fromDb?.name).toBe("New Project Name") - }) + const fromDb = Project.get(project.id) + expect(fromDb?.name).toBe("New Project Name") + }), + ) - test("should update icon url", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should update icon url", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - const updated = await run((svc) => - svc.update({ - projectID: project.id, - icon: { url: "https://example.com/icon.png" }, - }), - ) + const updated = yield* run((svc) => + svc.update({ + projectID: project.id, + icon: { url: "https://example.com/icon.png" }, + }), + ) - expect(updated.icon?.url).toBe("https://example.com/icon.png") + expect(updated.icon?.url).toBe("https://example.com/icon.png") - const fromDb = Project.get(project.id) - expect(fromDb?.icon?.url).toBe("https://example.com/icon.png") - }) + const fromDb = Project.get(project.id) + expect(fromDb?.icon?.url).toBe("https://example.com/icon.png") + }), + ) - test("should update icon color", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should update icon color", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - const updated = await run((svc) => - svc.update({ - projectID: project.id, - icon: { color: "#ff0000" }, - }), - ) + const updated = yield* run((svc) => + svc.update({ + projectID: project.id, + icon: { color: "#ff0000" }, + }), + ) - expect(updated.icon?.color).toBe("#ff0000") + expect(updated.icon?.color).toBe("#ff0000") - const fromDb = Project.get(project.id) - expect(fromDb?.icon?.color).toBe("#ff0000") - }) + const fromDb = Project.get(project.id) + expect(fromDb?.icon?.color).toBe("#ff0000") + }), + ) - test("should update icon override", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should update icon override", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - const updated = await run((svc) => - svc.update({ - projectID: project.id, - icon: { override: "data:image/png;base64,abc123" }, - }), - ) + const updated = yield* run((svc) => + svc.update({ + projectID: project.id, + icon: { override: "data:image/png;base64,abc123" }, + }), + ) - expect(updated.icon?.override).toBe("data:image/png;base64,abc123") + expect(updated.icon?.override).toBe("data:image/png;base64,abc123") - const fromDb = Project.get(project.id) - expect(fromDb?.icon?.override).toBe("data:image/png;base64,abc123") - }) + const fromDb = Project.get(project.id) + expect(fromDb?.icon?.override).toBe("data:image/png;base64,abc123") + }), + ) - test("should update commands", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should update commands", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - const updated = await run((svc) => - svc.update({ - projectID: project.id, - commands: { start: "npm run dev" }, - }), - ) + const updated = yield* run((svc) => + svc.update({ + projectID: project.id, + commands: { start: "npm run dev" }, + }), + ) - expect(updated.commands?.start).toBe("npm run dev") + expect(updated.commands?.start).toBe("npm run dev") - const fromDb = Project.get(project.id) - expect(fromDb?.commands?.start).toBe("npm run dev") - }) + const fromDb = Project.get(project.id) + expect(fromDb?.commands?.start).toBe("npm run dev") + }), + ) - test("should throw error when project not found", async () => { - await expect( - run((svc) => + it.live("should throw error when project not found", () => + Effect.gen(function* () { + const exit = yield* run((svc) => svc.update({ projectID: ProjectID.make("nonexistent-project-id"), name: "Should Fail", }), - ), - ).rejects.toThrow("Project not found: nonexistent-project-id") - }) + ).pipe(Effect.exit) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + const error = Cause.squash(exit.cause) + expect(error instanceof Error ? error.message : String(error)).toContain("Project not found: nonexistent-project-id") + } + }), + ) - test("should emit GlobalBus event on update", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("should emit GlobalBus event on update", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - let eventPayload: any = null - const on = (data: any) => { - eventPayload = data - } - GlobalBus.on("event", on) + let eventPayload: any = null + const on = (data: any) => { + eventPayload = data + } + GlobalBus.on("event", on) + yield* Effect.addFinalizer(() => Effect.sync(() => GlobalBus.off("event", on))) - try { - await run((svc) => svc.update({ projectID: project.id, name: "Updated Name" })) + yield* run((svc) => svc.update({ projectID: project.id, name: "Updated Name" })) expect(eventPayload).not.toBeNull() expect(eventPayload.payload.type).toBe("project.updated") expect(eventPayload.payload.properties.name).toBe("Updated Name") - } finally { - GlobalBus.off("event", on) - } - }) + }), + ) - test("should update multiple fields at once", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) - - const updated = await run((svc) => - svc.update({ - projectID: project.id, - name: "Multi Update", - icon: { url: "https://example.com/favicon.ico", override: "data:image/png;base64,abc123", color: "#00ff00" }, - commands: { start: "make start" }, - }), - ) - - expect(updated.name).toBe("Multi Update") - expect(updated.icon?.url).toBe("https://example.com/favicon.ico") - expect(updated.icon?.override).toBe("data:image/png;base64,abc123") - expect(updated.icon?.color).toBe("#00ff00") - expect(updated.commands?.start).toBe("make start") - }) + it.live("should update multiple fields at once", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) + + const updated = yield* run((svc) => + svc.update({ + projectID: project.id, + name: "Multi Update", + icon: { url: "https://example.com/favicon.ico", override: "data:image/png;base64,abc123", color: "#00ff00" }, + commands: { start: "make start" }, + }), + ) + + expect(updated.name).toBe("Multi Update") + expect(updated.icon?.url).toBe("https://example.com/favicon.ico") + expect(updated.icon?.override).toBe("data:image/png;base64,abc123") + expect(updated.icon?.color).toBe("#00ff00") + expect(updated.commands?.start).toBe("make start") + }), + ) }) describe("Project.list and Project.get", () => { - test("list returns all projects", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("list returns all projects", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - const all = Project.list() - expect(all.length).toBeGreaterThan(0) - expect(all.find((p) => p.id === project.id)).toBeDefined() - }) + const all = Project.list() + expect(all.length).toBeGreaterThan(0) + expect(all.find((p) => p.id === project.id)).toBeDefined() + }), + ) - test("get returns project by id", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("get returns project by id", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - const found = Project.get(project.id) - expect(found).toBeDefined() - expect(found!.id).toBe(project.id) - }) + const found = Project.get(project.id) + expect(found).toBeDefined() + expect(found!.id).toBe(project.id) + }), + ) test("get returns undefined for unknown id", () => { const found = Project.get(ProjectID.make("nonexistent")) @@ -472,65 +517,72 @@ describe("Project.list and Project.get", () => { }) describe("Project.setInitialized", () => { - test("sets time_initialized on project", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + it.live("sets time_initialized on project", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) - expect(project.time.initialized).toBeUndefined() + expect(project.time.initialized).toBeUndefined() - Project.setInitialized(project.id) + Project.setInitialized(project.id) - const updated = Project.get(project.id) - expect(updated?.time.initialized).toBeDefined() - }) + const updated = Project.get(project.id) + expect(updated?.time.initialized).toBeDefined() + }), + ) }) describe("Project.addSandbox and Project.removeSandbox", () => { - test("addSandbox adds directory and removeSandbox removes it", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) - const sandboxDir = path.join(tmp.path, "sandbox-test") + it.live("addSandbox adds directory and removeSandbox removes it", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) + const sandboxDir = path.join(tmp, "sandbox-test") - await run((svc) => svc.addSandbox(project.id, sandboxDir)) + yield* run((svc) => svc.addSandbox(project.id, sandboxDir)) - let found = Project.get(project.id) - expect(found?.sandboxes).toContain(sandboxDir) + let found = Project.get(project.id) + expect(found?.sandboxes).toContain(sandboxDir) - await run((svc) => svc.removeSandbox(project.id, sandboxDir)) + yield* run((svc) => svc.removeSandbox(project.id, sandboxDir)) - found = Project.get(project.id) - expect(found?.sandboxes).not.toContain(sandboxDir) - }) + found = Project.get(project.id) + expect(found?.sandboxes).not.toContain(sandboxDir) + }), + ) - test("addSandbox emits GlobalBus event", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) - const sandboxDir = path.join(tmp.path, "sandbox-event") + it.live("addSandbox emits GlobalBus event", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() + const { project } = yield* run((svc) => svc.fromDirectory(tmp)) + const sandboxDir = path.join(tmp, "sandbox-event") - const events: any[] = [] - const on = (evt: any) => events.push(evt) - GlobalBus.on("event", on) + const events: any[] = [] + const on = (evt: any) => events.push(evt) + GlobalBus.on("event", on) + yield* Effect.addFinalizer(() => Effect.sync(() => GlobalBus.off("event", on))) - await run((svc) => svc.addSandbox(project.id, sandboxDir)) + yield* run((svc) => svc.addSandbox(project.id, sandboxDir)) - GlobalBus.off("event", on) - expect(events.some((e) => e.payload.type === Project.Event.Updated.type)).toBe(true) - }) + expect(events.some((e) => e.payload.type === Project.Event.Updated.type)).toBe(true) + }), + ) }) describe("Project.fromDirectory with bare repos", () => { - test("worktree from bare repo should cache in bare repo, not parent", async () => { - await using tmp = await tmpdir({ git: true }) + it.live("worktree from bare repo should cache in bare repo, not parent", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() - const parentDir = path.dirname(tmp.path) - const barePath = path.join(parentDir, `bare-${Date.now()}.git`) - const worktreePath = path.join(parentDir, `worktree-${Date.now()}`) + const parentDir = path.dirname(tmp) + const barePath = path.join(parentDir, `bare-${Date.now()}.git`) + const worktreePath = path.join(parentDir, `worktree-${Date.now()}`) + yield* Effect.addFinalizer(() => Effect.promise(() => $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow()).pipe(Effect.ignore)) - try { - await $`git clone --bare ${tmp.path} ${barePath}`.quiet() - await $`git worktree add ${worktreePath} HEAD`.cwd(barePath).quiet() + yield* Effect.promise(() => $`git clone --bare ${tmp} ${barePath}`.quiet()) + yield* Effect.promise(() => $`git worktree add ${worktreePath} HEAD`.cwd(barePath).quiet()) - const { project } = await run((svc) => svc.fromDirectory(worktreePath)) + const { project } = yield* run((svc) => svc.fromDirectory(worktreePath)) expect(project.id).not.toBe(ProjectID.global) expect(project.worktree).toBe(barePath) @@ -538,31 +590,34 @@ describe("Project.fromDirectory with bare repos", () => { const correctCache = path.join(barePath, "opencode") const wrongCache = path.join(parentDir, ".git", "opencode") - expect(await Bun.file(correctCache).exists()).toBe(true) - expect(await Bun.file(wrongCache).exists()).toBe(false) - } finally { - await $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow() - } - }) - - test("different bare repos under same parent should not share project ID", async () => { - await using tmp1 = await tmpdir({ git: true }) - await using tmp2 = await tmpdir({ git: true }) + expect(yield* Effect.promise(() => Bun.file(correctCache).exists())).toBe(true) + expect(yield* Effect.promise(() => Bun.file(wrongCache).exists())).toBe(false) + }), + ) - const parentDir = path.dirname(tmp1.path) - const bareA = path.join(parentDir, `bare-a-${Date.now()}.git`) - const bareB = path.join(parentDir, `bare-b-${Date.now()}.git`) - const worktreeA = path.join(parentDir, `wt-a-${Date.now()}`) - const worktreeB = path.join(parentDir, `wt-b-${Date.now()}`) + it.live("different bare repos under same parent should not share project ID", () => + Effect.gen(function* () { + const tmp1 = yield* gitTmpdir() + const tmp2 = yield* gitTmpdir() + + const parentDir = path.dirname(tmp1) + const bareA = path.join(parentDir, `bare-a-${Date.now()}.git`) + const bareB = path.join(parentDir, `bare-b-${Date.now()}.git`) + const worktreeA = path.join(parentDir, `wt-a-${Date.now()}`) + const worktreeB = path.join(parentDir, `wt-b-${Date.now()}`) + yield* Effect.addFinalizer(() => + Effect.promise(() => $`rm -rf ${bareA} ${bareB} ${worktreeA} ${worktreeB}`.quiet().nothrow()).pipe( + Effect.ignore, + ), + ) - try { - await $`git clone --bare ${tmp1.path} ${bareA}`.quiet() - await $`git clone --bare ${tmp2.path} ${bareB}`.quiet() - await $`git worktree add ${worktreeA} HEAD`.cwd(bareA).quiet() - await $`git worktree add ${worktreeB} HEAD`.cwd(bareB).quiet() + yield* Effect.promise(() => $`git clone --bare ${tmp1} ${bareA}`.quiet()) + yield* Effect.promise(() => $`git clone --bare ${tmp2} ${bareB}`.quiet()) + yield* Effect.promise(() => $`git worktree add ${worktreeA} HEAD`.cwd(bareA).quiet()) + yield* Effect.promise(() => $`git worktree add ${worktreeB} HEAD`.cwd(bareB).quiet()) - const { project: projA } = await run((svc) => svc.fromDirectory(worktreeA)) - const { project: projB } = await run((svc) => svc.fromDirectory(worktreeB)) + const { project: projA } = yield* run((svc) => svc.fromDirectory(worktreeA)) + const { project: projB } = yield* run((svc) => svc.fromDirectory(worktreeB)) expect(projA.id).not.toBe(projB.id) @@ -570,34 +625,31 @@ describe("Project.fromDirectory with bare repos", () => { const cacheB = path.join(bareB, "opencode") const wrongCache = path.join(parentDir, ".git", "opencode") - expect(await Bun.file(cacheA).exists()).toBe(true) - expect(await Bun.file(cacheB).exists()).toBe(true) - expect(await Bun.file(wrongCache).exists()).toBe(false) - } finally { - await $`rm -rf ${bareA} ${bareB} ${worktreeA} ${worktreeB}`.quiet().nothrow() - } - }) + expect(yield* Effect.promise(() => Bun.file(cacheA).exists())).toBe(true) + expect(yield* Effect.promise(() => Bun.file(cacheB).exists())).toBe(true) + expect(yield* Effect.promise(() => Bun.file(wrongCache).exists())).toBe(false) + }), + ) - test("bare repo without .git suffix is still detected via core.bare", async () => { - await using tmp = await tmpdir({ git: true }) + it.live("bare repo without .git suffix is still detected via core.bare", () => + Effect.gen(function* () { + const tmp = yield* gitTmpdir() - const parentDir = path.dirname(tmp.path) - const barePath = path.join(parentDir, `bare-no-suffix-${Date.now()}`) - const worktreePath = path.join(parentDir, `worktree-${Date.now()}`) + const parentDir = path.dirname(tmp) + const barePath = path.join(parentDir, `bare-no-suffix-${Date.now()}`) + const worktreePath = path.join(parentDir, `worktree-${Date.now()}`) + yield* Effect.addFinalizer(() => Effect.promise(() => $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow()).pipe(Effect.ignore)) - try { - await $`git clone --bare ${tmp.path} ${barePath}`.quiet() - await $`git worktree add ${worktreePath} HEAD`.cwd(barePath).quiet() + yield* Effect.promise(() => $`git clone --bare ${tmp} ${barePath}`.quiet()) + yield* Effect.promise(() => $`git worktree add ${worktreePath} HEAD`.cwd(barePath).quiet()) - const { project } = await run((svc) => svc.fromDirectory(worktreePath)) + const { project } = yield* run((svc) => svc.fromDirectory(worktreePath)) expect(project.id).not.toBe(ProjectID.global) expect(project.worktree).toBe(barePath) const correctCache = path.join(barePath, "opencode") - expect(await Bun.file(correctCache).exists()).toBe(true) - } finally { - await $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow() - } - }) + expect(yield* Effect.promise(() => Bun.file(correctCache).exists())).toBe(true) + }), + ) }) From f7dbb4dac464aaf0c47dc222e314f68777ebdf06 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 19:48:35 +0000 Subject: [PATCH 134/378] chore: generate --- .../opencode/test/project/project.test.ts | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 9ed1e60bb..9b599c2d6 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -85,7 +85,8 @@ function projectLayerWithFailure(failArg: string) { ) } -const failureIt = (failArg: string) => testEffect(Layer.mergeAll(projectLayerWithFailure(failArg), CrossSpawnSpawner.defaultLayer)) +const failureIt = (failArg: string) => + testEffect(Layer.mergeAll(projectLayerWithFailure(failArg), CrossSpawnSpawner.defaultLayer)) describe("Project.fromDirectory", () => { it.live("should handle git repository with no commits", () => @@ -192,7 +193,14 @@ describe("Project.fromDirectory with worktrees", () => { const tmp = yield* gitTmpdir() const worktreePath = path.join(tmp, "..", path.basename(tmp) + "-worktree") - yield* Effect.addFinalizer(() => Effect.promise(() => $`git worktree remove ${worktreePath}`.cwd(tmp).quiet().catch(() => {}))) + yield* Effect.addFinalizer(() => + Effect.promise(() => + $`git worktree remove ${worktreePath}` + .cwd(tmp) + .quiet() + .catch(() => {}), + ), + ) yield* Effect.promise(() => $`git worktree add ${worktreePath} -b test-branch-${Date.now()}`.cwd(tmp).quiet()) const { project, sandbox } = yield* run((svc) => svc.fromDirectory(worktreePath)) @@ -211,7 +219,14 @@ describe("Project.fromDirectory with worktrees", () => { const { project: main } = yield* run((svc) => svc.fromDirectory(tmp)) const worktreePath = path.join(tmp, "..", path.basename(tmp) + "-wt-shared") - yield* Effect.addFinalizer(() => Effect.promise(() => $`git worktree remove ${worktreePath}`.cwd(tmp).quiet().catch(() => {}))) + yield* Effect.addFinalizer(() => + Effect.promise(() => + $`git worktree remove ${worktreePath}` + .cwd(tmp) + .quiet() + .catch(() => {}), + ), + ) yield* Effect.promise(() => $`git worktree add ${worktreePath} -b shared-${Date.now()}`.cwd(tmp).quiet()) const { project: wt } = yield* run((svc) => svc.fromDirectory(worktreePath)) @@ -229,10 +244,12 @@ describe("Project.fromDirectory with worktrees", () => { Effect.gen(function* () { const tmp = yield* gitTmpdir() - // Create a bare remote, push, then clone into a second directory + // Create a bare remote, push, then clone into a second directory const bare = tmp + "-bare" const clone = tmp + "-clone" - yield* Effect.addFinalizer(() => Effect.promise(() => $`rm -rf ${bare} ${clone}`.quiet().nothrow()).pipe(Effect.ignore)) + yield* Effect.addFinalizer(() => + Effect.promise(() => $`rm -rf ${bare} ${clone}`.quiet().nothrow()).pipe(Effect.ignore), + ) yield* Effect.promise(() => $`git clone --bare ${tmp} ${bare}`.quiet()) yield* Effect.promise(() => $`git clone ${bare} ${clone}`.quiet()) @@ -251,8 +268,18 @@ describe("Project.fromDirectory with worktrees", () => { const worktree2 = path.join(tmp, "..", path.basename(tmp) + "-wt2") yield* Effect.addFinalizer(() => Effect.gen(function* () { - yield* Effect.promise(() => $`git worktree remove ${worktree1}`.cwd(tmp).quiet().catch(() => {})) - yield* Effect.promise(() => $`git worktree remove ${worktree2}`.cwd(tmp).quiet().catch(() => {})) + yield* Effect.promise(() => + $`git worktree remove ${worktree1}` + .cwd(tmp) + .quiet() + .catch(() => {}), + ) + yield* Effect.promise(() => + $`git worktree remove ${worktree2}` + .cwd(tmp) + .quiet() + .catch(() => {}), + ) }), ) yield* Effect.promise(() => $`git worktree add ${worktree1} -b branch-${Date.now()}`.cwd(tmp).quiet()) @@ -439,7 +466,9 @@ describe("Project.update", () => { expect(Exit.isFailure(exit)).toBe(true) if (Exit.isFailure(exit)) { const error = Cause.squash(exit.cause) - expect(error instanceof Error ? error.message : String(error)).toContain("Project not found: nonexistent-project-id") + expect(error instanceof Error ? error.message : String(error)).toContain( + "Project not found: nonexistent-project-id", + ) } }), ) @@ -577,7 +606,9 @@ describe("Project.fromDirectory with bare repos", () => { const parentDir = path.dirname(tmp) const barePath = path.join(parentDir, `bare-${Date.now()}.git`) const worktreePath = path.join(parentDir, `worktree-${Date.now()}`) - yield* Effect.addFinalizer(() => Effect.promise(() => $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow()).pipe(Effect.ignore)) + yield* Effect.addFinalizer(() => + Effect.promise(() => $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow()).pipe(Effect.ignore), + ) yield* Effect.promise(() => $`git clone --bare ${tmp} ${barePath}`.quiet()) yield* Effect.promise(() => $`git worktree add ${worktreePath} HEAD`.cwd(barePath).quiet()) @@ -638,7 +669,9 @@ describe("Project.fromDirectory with bare repos", () => { const parentDir = path.dirname(tmp) const barePath = path.join(parentDir, `bare-no-suffix-${Date.now()}`) const worktreePath = path.join(parentDir, `worktree-${Date.now()}`) - yield* Effect.addFinalizer(() => Effect.promise(() => $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow()).pipe(Effect.ignore)) + yield* Effect.addFinalizer(() => + Effect.promise(() => $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow()).pipe(Effect.ignore), + ) yield* Effect.promise(() => $`git clone --bare ${tmp} ${barePath}`.quiet()) yield* Effect.promise(() => $`git worktree add ${worktreePath} HEAD`.cwd(barePath).quiet()) From e0d0fe1ff79b2e68fbf496f3957b63aa5bcd53a8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 15:58:54 -0400 Subject: [PATCH 135/378] test(bus): migrate integration tests to Effect runner (#27132) --- .../opencode/test/bus/bus-integration.test.ts | 134 +++++++++--------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/packages/opencode/test/bus/bus-integration.test.ts b/packages/opencode/test/bus/bus-integration.test.ts index 3e3d7a3e9..645a94fb3 100644 --- a/packages/opencode/test/bus/bus-integration.test.ts +++ b/packages/opencode/test/bus/bus-integration.test.ts @@ -1,88 +1,88 @@ -import { afterEach, describe, expect, test } from "bun:test" -import { Schema } from "effect" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { afterEach, describe, expect } from "bun:test" +import { Deferred, Effect, Layer, Schema } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" const TestEvent = BusEvent.define("test.integration", Schema.Struct({ value: Schema.Number })) - -function withInstance(directory: string, fn: () => Promise) { - return WithInstance.provide({ directory, fn }) -} +const it = testEffect(Layer.mergeAll(Bus.layer, CrossSpawnSpawner.defaultLayer)) describe("Bus integration: acquireRelease subscriber pattern", () => { afterEach(() => disposeAllInstances()) - test("subscriber via callback facade receives events and cleans up on unsub", async () => { - await using tmp = await tmpdir() - const received: number[] = [] + it.instance("subscriber via callback facade receives events and cleans up on unsub", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const received: number[] = [] + const receivedTwo = yield* Deferred.make() - await withInstance(tmp.path, async () => { - const unsub = Bus.subscribe(TestEvent, (evt) => { + const unsub = yield* bus.subscribeCallback(TestEvent, (evt) => { received.push(evt.properties.value) + if (received.length === 2) Deferred.doneUnsafe(receivedTwo, Effect.void) }) - await Bun.sleep(10) - await Bus.publish(TestEvent, { value: 1 }) - await Bus.publish(TestEvent, { value: 2 }) - await Bun.sleep(10) + yield* bus.publish(TestEvent, { value: 1 }) + yield* bus.publish(TestEvent, { value: 2 }) + yield* Deferred.await(receivedTwo).pipe(Effect.timeout("2 seconds")) expect(received).toEqual([1, 2]) - unsub() - await Bun.sleep(10) - await Bus.publish(TestEvent, { value: 3 }) - await Bun.sleep(10) + yield* Effect.sync(unsub) + yield* bus.publish(TestEvent, { value: 3 }) + yield* Effect.sleep("10 millis") expect(received).toEqual([1, 2]) - }) - }) - - test("subscribeAll receives events from multiple types", async () => { - await using tmp = await tmpdir() - const received: Array<{ type: string; value?: number }> = [] + }), + ) - const OtherEvent = BusEvent.define("test.other", Schema.Struct({ value: Schema.Number })) + it.instance("subscribeAll receives events from multiple types", () => + Effect.gen(function* () { + const bus = yield* Bus.Service + const received: Array<{ type: string; value?: number }> = [] + const OtherEvent = BusEvent.define("test.other", Schema.Struct({ value: Schema.Number })) + const receivedTwo = yield* Deferred.make() - await withInstance(tmp.path, async () => { - Bus.subscribeAll((evt) => { + yield* bus.subscribeAllCallback((evt) => { received.push({ type: evt.type, value: evt.properties.value }) + if (received.length === 2) Deferred.doneUnsafe(receivedTwo, Effect.void) }) - await Bun.sleep(10) - await Bus.publish(TestEvent, { value: 10 }) - await Bus.publish(OtherEvent, { value: 20 }) - await Bun.sleep(10) - }) - - expect(received).toEqual([ - { type: "test.integration", value: 10 }, - { type: "test.other", value: 20 }, - ]) - }) - - test("subscriber cleanup on instance disposal interrupts the stream", async () => { - await using tmp = await tmpdir() - const received: number[] = [] - let disposed = false - - await withInstance(tmp.path, async () => { - Bus.subscribeAll((evt) => { - if (evt.type === Bus.InstanceDisposed.type) { - disposed = true - return - } - received.push(evt.properties.value) - }) - await Bun.sleep(10) - await Bus.publish(TestEvent, { value: 1 }) - await Bun.sleep(10) - }) - - await disposeAllInstances() - await Bun.sleep(50) - - expect(received).toEqual([1]) - expect(disposed).toBe(true) - }) + yield* bus.publish(TestEvent, { value: 10 }) + yield* bus.publish(OtherEvent, { value: 20 }) + yield* Deferred.await(receivedTwo).pipe(Effect.timeout("2 seconds")) + + expect(received).toEqual([ + { type: "test.integration", value: 10 }, + { type: "test.other", value: 20 }, + ]) + }), + ) + + it.live("subscriber cleanup on instance disposal interrupts the stream", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const received: number[] = [] + const seen = yield* Deferred.make() + const disposed = yield* Deferred.make() + + yield* Effect.gen(function* () { + const bus = yield* Bus.Service + yield* bus.subscribeAllCallback((evt) => { + if (evt.type === Bus.InstanceDisposed.type) { + Deferred.doneUnsafe(disposed, Effect.void) + return + } + received.push(evt.properties.value) + Deferred.doneUnsafe(seen, Effect.void) + }) + yield* bus.publish(TestEvent, { value: 1 }) + yield* Deferred.await(seen).pipe(Effect.timeout("2 seconds")) + }).pipe(provideInstance(dir)) + + yield* Effect.promise(() => disposeAllInstances()) + yield* Deferred.await(disposed).pipe(Effect.timeout("2 seconds")) + + expect(received).toEqual([1]) + }), + ) }) From 3f74abc6cd8a24eaa99d5a8ab00b1c905b9eff20 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:00:54 -0400 Subject: [PATCH 136/378] test: simplify Effect migration follow-ups (#27136) --- packages/opencode/test/fixture/fixture.ts | 2 +- .../opencode/test/project/project.test.ts | 69 ++++++++----------- .../opencode/test/question/question.test.ts | 3 +- 3 files changed, 31 insertions(+), 43 deletions(-) diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts index d47620f62..fedbc246b 100644 --- a/packages/opencode/test/fixture/fixture.ts +++ b/packages/opencode/test/fixture/fixture.ts @@ -135,7 +135,7 @@ export function tmpdirScoped(options?: { git?: boolean; config?: Partial(fn: (svc: Project.Interface) => Effect.Effect) { }) } -function gitTmpdir() { - return Effect.gen(function* () { - const tmp = yield* tmpdirScoped() - yield* Effect.promise(() => $`git init`.cwd(tmp).quiet()) - yield* Effect.promise(() => $`git config core.fsmonitor false`.cwd(tmp).quiet()) - yield* Effect.promise(() => $`git config commit.gpgsign false`.cwd(tmp).quiet()) - yield* Effect.promise(() => $`git config user.email "test@opencode.test"`.cwd(tmp).quiet()) - yield* Effect.promise(() => $`git config user.name "Test"`.cwd(tmp).quiet()) - yield* Effect.promise(() => $`git commit --allow-empty -m ${`root commit ${tmp}`}`.cwd(tmp).quiet()) - return tmp - }) -} - /** * Creates a mock ChildProcessSpawner layer that intercepts git subcommands * matching `failArg` and returns exit code 128, while delegating everything @@ -108,7 +95,7 @@ describe("Project.fromDirectory", () => { it.live("should handle git repository with commits", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) @@ -132,7 +119,7 @@ describe("Project.fromDirectory", () => { it.live("derives stable project ID from root commit", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project: a } = yield* run((svc) => svc.fromDirectory(tmp)) const { project: b } = yield* run((svc) => svc.fromDirectory(tmp)) expect(b.id).toBe(a.id) @@ -156,7 +143,7 @@ describe("Project.fromDirectory git failure paths", () => { failureIt("--show-toplevel").live("handles show-toplevel failure gracefully", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project, sandbox } = yield* run((svc) => svc.fromDirectory(tmp)) expect(project.worktree).toBe(tmp) @@ -166,7 +153,7 @@ describe("Project.fromDirectory git failure paths", () => { failureIt("--git-common-dir").live("handles git-common-dir failure gracefully", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project, sandbox } = yield* run((svc) => svc.fromDirectory(tmp)) expect(project.worktree).toBe(tmp) @@ -178,7 +165,7 @@ describe("Project.fromDirectory git failure paths", () => { describe("Project.fromDirectory with worktrees", () => { it.live("should set worktree to root when called from root", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project, sandbox } = yield* run((svc) => svc.fromDirectory(tmp)) @@ -190,7 +177,7 @@ describe("Project.fromDirectory with worktrees", () => { it.live("should set worktree to root when called from a worktree", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const worktreePath = path.join(tmp, "..", path.basename(tmp) + "-worktree") yield* Effect.addFinalizer(() => @@ -214,7 +201,7 @@ describe("Project.fromDirectory with worktrees", () => { it.live("worktree should share project ID with main repo", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project: main } = yield* run((svc) => svc.fromDirectory(tmp)) @@ -242,7 +229,7 @@ describe("Project.fromDirectory with worktrees", () => { it.live("separate clones of the same repo should share project ID", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) // Create a bare remote, push, then clone into a second directory const bare = tmp + "-bare" @@ -262,7 +249,7 @@ describe("Project.fromDirectory with worktrees", () => { it.live("should accumulate multiple worktrees in sandboxes", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const worktree1 = path.join(tmp, "..", path.basename(tmp) + "-wt1") const worktree2 = path.join(tmp, "..", path.basename(tmp) + "-wt2") @@ -299,7 +286,7 @@ describe("Project.fromDirectory with worktrees", () => { describe("Project.discover", () => { it.live("should discover favicon.png in root", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) @@ -318,7 +305,7 @@ describe("Project.discover", () => { it.live("should not discover non-image files", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) yield* Effect.promise(() => Bun.write(path.join(tmp, "favicon.txt"), "not an image")) @@ -333,7 +320,7 @@ describe("Project.discover", () => { it.live("should not discover favicon when override is set", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) yield* run((svc) => @@ -362,7 +349,7 @@ describe("Project.discover", () => { describe("Project.update", () => { it.live("should update name", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const updated = yield* run((svc) => @@ -381,7 +368,7 @@ describe("Project.update", () => { it.live("should update icon url", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const updated = yield* run((svc) => @@ -400,7 +387,7 @@ describe("Project.update", () => { it.live("should update icon color", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const updated = yield* run((svc) => @@ -419,7 +406,7 @@ describe("Project.update", () => { it.live("should update icon override", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const updated = yield* run((svc) => @@ -438,7 +425,7 @@ describe("Project.update", () => { it.live("should update commands", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const updated = yield* run((svc) => @@ -475,7 +462,7 @@ describe("Project.update", () => { it.live("should emit GlobalBus event on update", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) let eventPayload: any = null @@ -495,7 +482,7 @@ describe("Project.update", () => { it.live("should update multiple fields at once", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const updated = yield* run((svc) => @@ -519,7 +506,7 @@ describe("Project.update", () => { describe("Project.list and Project.get", () => { it.live("list returns all projects", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const all = Project.list() @@ -530,7 +517,7 @@ describe("Project.list and Project.get", () => { it.live("get returns project by id", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const found = Project.get(project.id) @@ -548,7 +535,7 @@ describe("Project.list and Project.get", () => { describe("Project.setInitialized", () => { it.live("sets time_initialized on project", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) expect(project.time.initialized).toBeUndefined() @@ -564,7 +551,7 @@ describe("Project.setInitialized", () => { describe("Project.addSandbox and Project.removeSandbox", () => { it.live("addSandbox adds directory and removeSandbox removes it", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const sandboxDir = path.join(tmp, "sandbox-test") @@ -582,7 +569,7 @@ describe("Project.addSandbox and Project.removeSandbox", () => { it.live("addSandbox emits GlobalBus event", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const { project } = yield* run((svc) => svc.fromDirectory(tmp)) const sandboxDir = path.join(tmp, "sandbox-event") @@ -601,7 +588,7 @@ describe("Project.addSandbox and Project.removeSandbox", () => { describe("Project.fromDirectory with bare repos", () => { it.live("worktree from bare repo should cache in bare repo, not parent", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const parentDir = path.dirname(tmp) const barePath = path.join(parentDir, `bare-${Date.now()}.git`) @@ -628,8 +615,8 @@ describe("Project.fromDirectory with bare repos", () => { it.live("different bare repos under same parent should not share project ID", () => Effect.gen(function* () { - const tmp1 = yield* gitTmpdir() - const tmp2 = yield* gitTmpdir() + const tmp1 = yield* tmpdirScoped({ git: true }) + const tmp2 = yield* tmpdirScoped({ git: true }) const parentDir = path.dirname(tmp1) const bareA = path.join(parentDir, `bare-a-${Date.now()}.git`) @@ -664,7 +651,7 @@ describe("Project.fromDirectory with bare repos", () => { it.live("bare repo without .git suffix is still detected via core.bare", () => Effect.gen(function* () { - const tmp = yield* gitTmpdir() + const tmp = yield* tmpdirScoped({ git: true }) const parentDir = path.dirname(tmp) const barePath = path.join(parentDir, `bare-no-suffix-${Date.now()}`) diff --git a/packages/opencode/test/question/question.test.ts b/packages/opencode/test/question/question.test.ts index a87907c57..3e970b63f 100644 --- a/packages/opencode/test/question/question.test.ts +++ b/packages/opencode/test/question/question.test.ts @@ -397,7 +397,8 @@ it.live("pending question rejects on instance dispose", () => }).pipe(provideInstance(dir), Effect.forkScoped) expect(yield* waitForPending(1).pipe(provideInstance(dir))).toHaveLength(1) - yield* Effect.promise(() => InstanceRuntime.disposeInstance(Instance.current)).pipe(provideInstance(dir)) + const ctx = yield* Effect.sync(() => Instance.current).pipe(provideInstance(dir)) + yield* Effect.promise(() => InstanceRuntime.disposeInstance(ctx)) const exit = yield* Fiber.await(fiber) expect(Exit.isFailure(exit)).toBe(true) From b9e7cbf13cdbce6434cf3152236b312db75afd95 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 12 May 2026 16:06:16 -0400 Subject: [PATCH 137/378] sync --- packages/console/app/src/routes/zen/util/handler.ts | 1 - 1 file changed, 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 2e46df036..540dfe7e8 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -299,7 +299,6 @@ export async function handler( let buffer = "" let responseLength = 0 let timestampFirstByte = 0 - let timestampLastByte = 0 function pump(): Promise { return ( From dd14413a642759dfdecb2adee42f56b4b314d08f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:16:58 -0400 Subject: [PATCH 138/378] Preserve native LLM tool context (#27116) --- packages/llm/example/tutorial.ts | 4 +- packages/llm/src/index.ts | 1 + .../llm/src/protocols/openai-responses.ts | 2 +- packages/llm/src/protocols/utils/lifecycle.ts | 2 +- packages/llm/src/schema/events.ts | 44 +++++---- packages/llm/src/tool-runtime.ts | 96 ++++++++++++++++--- packages/llm/src/tool.ts | 11 ++- packages/llm/test/adapter.test.ts | 6 +- packages/llm/test/llm.test.ts | 2 +- .../test/provider/anthropic-messages.test.ts | 6 +- .../test/provider/bedrock-converse.test.ts | 8 +- packages/llm/test/provider/gemini.test.ts | 14 +-- .../llm/test/provider/openai-chat.test.ts | 4 +- .../provider/openai-compatible-chat.test.ts | 2 +- .../test/provider/openai-responses.test.ts | 4 +- packages/llm/test/recorded-scenarios.ts | 16 ++-- packages/llm/test/schema.test.ts | 5 + packages/llm/test/tool-runtime.test.ts | 92 ++++++++++++++++-- 18 files changed, 244 insertions(+), 75 deletions(-) diff --git a/packages/llm/example/tutorial.ts b/packages/llm/example/tutorial.ts index a9adecf36..429ac4824 100644 --- a/packages/llm/example/tutorial.ts +++ b/packages/llm/example/tutorial.ts @@ -78,7 +78,7 @@ const streamText = LLM.stream(request).pipe( Stream.tap((event) => Effect.sync(() => { if (event.type === "text-delta") process.stdout.write(`\ntext: ${event.text}`) - if (event.type === "request-finish") process.stdout.write(`\nfinish: ${event.reason}\n`) + if (event.type === "finish") process.stdout.write(`\nfinish: ${event.reason}\n`) }), ), Stream.runDrain, @@ -185,7 +185,7 @@ const FakeProtocol = Protocol.make({ event: Schema.String, initial: () => undefined, step: (_, frame) => Effect.succeed([undefined, [{ type: "text-delta", id: "text-0", text: frame }]] as const), - onHalt: () => [{ type: "request-finish", reason: "stop" }], + onHalt: () => [{ type: "finish", reason: "stop" }], }, }) diff --git a/packages/llm/src/index.ts b/packages/llm/src/index.ts index f4adf4859..acf73b360 100644 --- a/packages/llm/src/index.ts +++ b/packages/llm/src/index.ts @@ -17,6 +17,7 @@ export type { ExecutableTools, Tool as ToolShape, ToolExecute, + ToolExecuteContext, Tools, ToolSchema, } from "./tool" diff --git a/packages/llm/src/protocols/openai-responses.ts b/packages/llm/src/protocols/openai-responses.ts index e31a42cd5..7cf734f02 100644 --- a/packages/llm/src/protocols/openai-responses.ts +++ b/packages/llm/src/protocols/openai-responses.ts @@ -380,7 +380,7 @@ type StepResult = readonly [ParserState, ReadonlyArray] const NO_EVENTS: StepResult["1"] = [] // `response.completed` / `response.incomplete` are clean finishes that emit a -// `request-finish` event; `response.failed` is a hard failure that emits a +// `finish` event; `response.failed` is a hard failure that emits a // `provider-error`. All three end the stream — kept in one set so `step` and // the protocol's `terminal` predicate stay in sync. const TERMINAL_TYPES = new Set(["response.completed", "response.incomplete", "response.failed"]) diff --git a/packages/llm/src/protocols/utils/lifecycle.ts b/packages/llm/src/protocols/utils/lifecycle.ts index 67039b137..c249d75ce 100644 --- a/packages/llm/src/protocols/utils/lifecycle.ts +++ b/packages/llm/src/protocols/utils/lifecycle.ts @@ -80,7 +80,7 @@ export const finish = ( usage: input.usage, providerMetadata: input.providerMetadata, }), - LLMEvent.requestFinish(input), + LLMEvent.finish(input), ) return { ...stepped, stepStarted: false } } diff --git a/packages/llm/src/schema/events.ts b/packages/llm/src/schema/events.ts index 6e6bb1541..6a088dc87 100644 --- a/packages/llm/src/schema/events.ts +++ b/packages/llm/src/schema/events.ts @@ -1,5 +1,5 @@ import { Schema } from "effect" -import { ContentBlockID, FinishReason, ProtocolID, ProviderMetadata, ResponseID, RouteID, ToolCallID } from "./ids" +import { ContentBlockID, FinishReason, ProtocolID, ProviderMetadata, RouteID, ToolCallID } from "./ids" import { ModelRef } from "./options" import { ToolResultValue } from "./messages" @@ -66,14 +66,13 @@ export class Usage extends Schema.Class("LLM.Usage")({ get visibleOutputTokens() { return Math.max(0, (this.outputTokens ?? 0) - (this.reasoningTokens ?? 0)) } + + static from(input: UsageInput) { + return input instanceof Usage ? input : new Usage(input) + } } -export const RequestStart = Schema.Struct({ - type: Schema.tag("request-start"), - id: ResponseID, - model: ModelRef, -}).annotate({ identifier: "LLM.Event.RequestStart" }) -export type RequestStart = Schema.Schema.Type +export type UsageInput = Usage | ConstructorParameters[0] export const StepStart = Schema.Struct({ type: Schema.tag("step-start"), @@ -185,13 +184,13 @@ export const StepFinish = Schema.Struct({ }).annotate({ identifier: "LLM.Event.StepFinish" }) export type StepFinish = Schema.Schema.Type -export const RequestFinish = Schema.Struct({ - type: Schema.tag("request-finish"), +export const Finish = Schema.Struct({ + type: Schema.tag("finish"), reason: FinishReason, usage: Schema.optional(Usage), providerMetadata: Schema.optional(ProviderMetadata), -}).annotate({ identifier: "LLM.Event.RequestFinish" }) -export type RequestFinish = Schema.Schema.Type +}).annotate({ identifier: "LLM.Event.Finish" }) +export type Finish = Schema.Schema.Type export const ProviderErrorEvent = Schema.Struct({ type: Schema.tag("provider-error"), @@ -202,7 +201,6 @@ export const ProviderErrorEvent = Schema.Struct({ export type ProviderErrorEvent = Schema.Schema.Type const llmEventTagged = Schema.Union([ - RequestStart, StepStart, TextStart, TextDelta, @@ -217,13 +215,15 @@ const llmEventTagged = Schema.Union([ ToolResult, ToolError, StepFinish, - RequestFinish, + Finish, ProviderErrorEvent, ]).pipe(Schema.toTaggedUnion("type")) type WithID = Omit & { readonly id: ID | string } +type WithUsage = Omit & { + readonly usage?: UsageInput +} -const responseID = (value: ResponseID | string) => ResponseID.make(value) const contentBlockID = (value: ContentBlockID | string) => ContentBlockID.make(value) const toolCallID = (value: ToolCallID | string) => ToolCallID.make(value) @@ -233,7 +233,6 @@ const toolCallID = (value: ToolCallID | string) => ToolCallID.make(value) * `events.filter(LLMEvent.guards["tool-call"])`. */ export const LLMEvent = Object.assign(llmEventTagged, { - requestStart: (input: WithID) => RequestStart.make({ ...input, id: responseID(input.id) }), stepStart: StepStart.make, textStart: (input: WithID) => TextStart.make({ ...input, id: contentBlockID(input.id) }), textDelta: (input: WithID) => TextDelta.make({ ...input, id: contentBlockID(input.id) }), @@ -252,11 +251,18 @@ export const LLMEvent = Object.assign(llmEventTagged, { toolCall: (input: WithID) => ToolCall.make({ ...input, id: toolCallID(input.id) }), toolResult: (input: WithID) => ToolResult.make({ ...input, id: toolCallID(input.id) }), toolError: (input: WithID) => ToolError.make({ ...input, id: toolCallID(input.id) }), - stepFinish: StepFinish.make, - requestFinish: RequestFinish.make, + stepFinish: (input: WithUsage) => + StepFinish.make({ + ...input, + usage: input.usage === undefined ? undefined : Usage.from(input.usage), + }), + finish: (input: WithUsage) => + Finish.make({ + ...input, + usage: input.usage === undefined ? undefined : Usage.from(input.usage), + }), providerError: ProviderErrorEvent.make, is: { - requestStart: llmEventTagged.guards["request-start"], stepStart: llmEventTagged.guards["step-start"], textStart: llmEventTagged.guards["text-start"], textDelta: llmEventTagged.guards["text-delta"], @@ -271,7 +277,7 @@ export const LLMEvent = Object.assign(llmEventTagged, { toolResult: llmEventTagged.guards["tool-result"], toolError: llmEventTagged.guards["tool-error"], stepFinish: llmEventTagged.guards["step-finish"], - requestFinish: llmEventTagged.guards["request-finish"], + finish: llmEventTagged.guards.finish, providerError: llmEventTagged.guards["provider-error"], }, }) diff --git a/packages/llm/src/tool-runtime.ts b/packages/llm/src/tool-runtime.ts index f46452582..d83dcc67a 100644 --- a/packages/llm/src/tool-runtime.ts +++ b/packages/llm/src/tool-runtime.ts @@ -12,6 +12,7 @@ import { ToolFailure, ToolResultPart, type ToolResultValue, + Usage, } from "./schema" import { type AnyTool, type ExecutableTools, type Tools, toDefinitions } from "./tool" @@ -72,19 +73,42 @@ export const stream = (options: StreamOptions): Stream.Strea tools: [...options.request.tools.filter((tool) => !runtimeToolNames.has(tool.name)), ...runtimeTools], }) - const loop = (request: LLMRequest, step: number): Stream.Stream => + const loop = ( + request: LLMRequest, + step: number, + usage: Usage | undefined, + providerMetadata: ProviderMetadata | undefined, + ): Stream.Stream => Stream.unwrap( Effect.gen(function* () { - const state: StepState = { assistantContent: [], toolCalls: [], finishReason: undefined } + const state: StepState = { + assistantContent: [], + toolCalls: [], + finishReason: undefined, + usage: undefined, + providerMetadata: undefined, + } const modelStream = options .stream(request) + .pipe(Stream.map((event) => indexStep(event, step))) .pipe(Stream.tap((event) => Effect.sync(() => accumulate(state, event)))) + .pipe(Stream.filter((event) => event.type !== "finish")) const continuation = Stream.unwrap( Effect.gen(function* () { - if (state.finishReason !== "tool-calls" || state.toolCalls.length === 0) return Stream.empty - if (options.toolExecution === "none") return Stream.empty + const totalUsage = addUsage(usage, state.usage) + const totalProviderMetadata = mergeProviderMetadata(providerMetadata, state.providerMetadata) + const finishStream = Stream.fromIterable([ + LLMEvent.finish({ + reason: state.finishReason ?? "unknown", + usage: totalUsage, + providerMetadata: totalProviderMetadata, + }), + ]) + + if (state.finishReason !== "tool-calls" || state.toolCalls.length === 0) return finishStream + if (options.toolExecution === "none") return finishStream const dispatched = yield* Effect.forEach( state.toolCalls, @@ -93,10 +117,14 @@ export const stream = (options: StreamOptions): Stream.Strea ) const resultStream = Stream.fromIterable(dispatched.flatMap(([call, result]) => emitEvents(call, result))) - if (!options.stopWhen) return resultStream - if (options.stopWhen({ step, request })) return resultStream + if (!options.stopWhen) return resultStream.pipe(Stream.concat(finishStream)) + if (options.stopWhen({ step, request })) return resultStream.pipe(Stream.concat(finishStream)) - return resultStream.pipe(Stream.concat(loop(followUpRequest(request, state, dispatched), step + 1))) + return resultStream.pipe( + Stream.concat( + loop(followUpRequest(request, state, dispatched), step + 1, totalUsage, totalProviderMetadata), + ), + ) }), ) @@ -104,13 +132,21 @@ export const stream = (options: StreamOptions): Stream.Strea }), ) - return loop(initialRequest, 0) + return loop(initialRequest, 0, undefined, undefined) +} + +const indexStep = (event: LLMEvent, index: number): LLMEvent => { + if (event.type === "step-start") return LLMEvent.stepStart({ index }) + if (event.type === "step-finish") return LLMEvent.stepFinish({ ...event, index }) + return event } interface StepState { assistantContent: ContentPart[] toolCalls: ToolCallPart[] finishReason: FinishReason | undefined + usage: Usage | undefined + providerMetadata: ProviderMetadata | undefined } const accumulate = (state: StepState, event: LLMEvent) => { @@ -154,11 +190,45 @@ const accumulate = (state: StepState, event: LLMEvent) => { ) return } - if (event.type === "step-finish" || event.type === "request-finish") { + if (event.type === "step-finish") { state.finishReason = event.reason === "stop" && state.toolCalls.length > 0 ? "tool-calls" : event.reason + state.usage = addUsage(state.usage, event.usage) + state.providerMetadata = mergeProviderMetadata(state.providerMetadata, event.providerMetadata) + return + } + if (event.type === "finish") { + state.finishReason ??= event.reason + state.usage ??= event.usage + state.providerMetadata = mergeProviderMetadata(state.providerMetadata, event.providerMetadata) } } +const addUsage = (left: Usage | undefined, right: Usage | undefined) => { + if (!left) return right + if (!right) return left + type UsageKey = + | "inputTokens" + | "outputTokens" + | "nonCachedInputTokens" + | "cacheReadInputTokens" + | "cacheWriteInputTokens" + | "reasoningTokens" + | "totalTokens" + const sum = (key: UsageKey) => + left[key] === undefined && right[key] === undefined ? undefined : Number(left[key] ?? 0) + Number(right[key] ?? 0) + + return new Usage({ + inputTokens: sum("inputTokens"), + outputTokens: sum("outputTokens"), + nonCachedInputTokens: sum("nonCachedInputTokens"), + cacheReadInputTokens: sum("cacheReadInputTokens"), + cacheWriteInputTokens: sum("cacheWriteInputTokens"), + reasoningTokens: sum("reasoningTokens"), + totalTokens: sum("totalTokens"), + providerMetadata: mergeProviderMetadata(left.providerMetadata, right.providerMetadata), + }) +} + const sameProviderMetadata = (left: ProviderMetadata | undefined, right: ProviderMetadata | undefined) => left === right || JSON.stringify(left) === JSON.stringify(right) @@ -200,17 +270,17 @@ const dispatch = (tools: Tools, call: ToolCallPart): Effect.Effect Effect.succeed({ type: "error" as const, value: failure.message } satisfies ToolResultValue), ), ) } -const decodeAndExecute = (tool: AnyTool, input: unknown): Effect.Effect => - tool._decode(input).pipe( +const decodeAndExecute = (tool: AnyTool, call: ToolCallPart): Effect.Effect => + tool._decode(call.input).pipe( Effect.mapError((error) => new ToolFailure({ message: `Invalid tool input: ${error.message}` })), - Effect.flatMap((decoded) => tool.execute!(decoded)), + Effect.flatMap((decoded) => tool.execute!(decoded, { id: call.id, name: call.name })), Effect.flatMap((value) => tool._encode(value).pipe( Effect.mapError( diff --git a/packages/llm/src/tool.ts b/packages/llm/src/tool.ts index 311c8798b..df0a1cd3d 100644 --- a/packages/llm/src/tool.ts +++ b/packages/llm/src/tool.ts @@ -1,5 +1,5 @@ import { Effect, JsonSchema, Schema } from "effect" -import type { ToolDefinition as ToolDefinitionClass } from "./schema" +import type { ToolCallPart, ToolDefinition as ToolDefinitionClass } from "./schema" import { ToolDefinition, ToolFailure } from "./schema" /** @@ -8,9 +8,14 @@ import { ToolDefinition, ToolFailure } from "./schema" * beyond pure data conversion belongs in the handler closure. */ export type ToolSchema = Schema.Codec +export interface ToolExecuteContext { + readonly id: ToolCallPart["id"] + readonly name: ToolCallPart["name"] +} export type ToolExecute, Success extends ToolSchema> = ( params: Schema.Schema.Type, + context?: ToolExecuteContext, ) => Effect.Effect, ToolFailure> /** @@ -61,7 +66,7 @@ type TypedToolConfig = { type DynamicToolConfig = { readonly description: string readonly jsonSchema: JsonSchema.JsonSchema - readonly execute?: (params: unknown) => Effect.Effect + readonly execute?: (params: unknown, context?: ToolExecuteContext) => Effect.Effect } /** @@ -110,7 +115,7 @@ export function make, Success extends ToolSch export function make(config: { readonly description: string readonly jsonSchema: JsonSchema.JsonSchema - readonly execute: (params: unknown) => Effect.Effect + readonly execute: (params: unknown, context?: ToolExecuteContext) => Effect.Effect }): AnyExecutableTool export function make(config: { readonly description: string diff --git a/packages/llm/test/adapter.test.ts b/packages/llm/test/adapter.test.ts index 5ac8b9d81..80349a5ae 100644 --- a/packages/llm/test/adapter.test.ts +++ b/packages/llm/test/adapter.test.ts @@ -51,7 +51,7 @@ const request = LLM.request({ const raiseEvent = (event: FakeEvent): import("../src/schema").LLMEvent => event.type === "finish" - ? { type: "request-finish", reason: event.reason } + ? { type: "finish", reason: event.reason } : { type: "text-delta", id: "text-0", text: event.text } const fakeProtocol = Protocol.make({ @@ -112,8 +112,8 @@ describe("llm route", () => { const events = Array.from(yield* llm.stream(request).pipe(Stream.runCollect)) const response = yield* llm.generate(request) - expect(events.map((event) => event.type)).toEqual(["text-delta", "request-finish"]) - expect(response.events.map((event) => event.type)).toEqual(["text-delta", "request-finish"]) + expect(events.map((event) => event.type)).toEqual(["text-delta", "finish"]) + expect(response.events.map((event) => event.type)).toEqual(["text-delta", "finish"]) }), ) diff --git a/packages/llm/test/llm.test.ts b/packages/llm/test/llm.test.ts index c01fe33b2..a20c48411 100644 --- a/packages/llm/test/llm.test.ts +++ b/packages/llm/test/llm.test.ts @@ -127,7 +127,7 @@ describe("llm constructors", () => { LLMResponse.text({ events: [ { type: "text-delta", id: "text-0", text: "hi" }, - { type: "request-finish", reason: "stop" }, + { type: "finish", reason: "stop" }, ], }), ).toBe("hi") diff --git a/packages/llm/test/provider/anthropic-messages.test.ts b/packages/llm/test/provider/anthropic-messages.test.ts index 6417f73c2..71204bcd6 100644 --- a/packages/llm/test/provider/anthropic-messages.test.ts +++ b/packages/llm/test/provider/anthropic-messages.test.ts @@ -124,7 +124,7 @@ describe("Anthropic Messages route", () => { providerMetadata: { anthropic: { signature: "sig_1" } }, }) expect(response.events.at(-1)).toMatchObject({ - type: "request-finish", + type: "finish", reason: "stop", providerMetadata: { anthropic: { stopSequence: "\n\nHuman:" } }, }) @@ -182,7 +182,7 @@ describe("Anthropic Messages route", () => { }, { type: "step-finish", index: 0, reason: "tool-calls", usage, providerMetadata: undefined }, { - type: "request-finish", + type: "finish", reason: "tool-calls", providerMetadata: undefined, usage, @@ -275,7 +275,7 @@ describe("Anthropic Messages route", () => { providerMetadata: { anthropic: { blockType: "web_search_tool_result" } }, }) expect(response.text).toBe("Found it.") - expect(response.events.at(-1)).toMatchObject({ type: "request-finish", reason: "stop" }) + expect(response.events.at(-1)).toMatchObject({ type: "finish", reason: "stop" }) }), ) diff --git a/packages/llm/test/provider/bedrock-converse.test.ts b/packages/llm/test/provider/bedrock-converse.test.ts index 7d1ad3f30..ffdd6e800 100644 --- a/packages/llm/test/provider/bedrock-converse.test.ts +++ b/packages/llm/test/provider/bedrock-converse.test.ts @@ -169,12 +169,12 @@ describe("Bedrock Converse route", () => { const response = yield* LLMClient.generate(baseRequest).pipe(Effect.provide(fixedBytes(body))) expect(response.text).toBe("Hello!") - const finishes = response.events.filter((event) => event.type === "request-finish") + const finishes = response.events.filter((event) => event.type === "finish") // Bedrock splits the finish across `messageStop` (carries reason) and // `metadata` (carries usage). We consolidate them into a single - // terminal `request-finish` event with both. + // terminal `finish` event with both. expect(finishes).toHaveLength(1) - expect(finishes[0]).toMatchObject({ type: "request-finish", reason: "stop" }) + expect(finishes[0]).toMatchObject({ type: "finish", reason: "stop" }) expect(response.usage).toMatchObject({ inputTokens: 5, outputTokens: 2, @@ -213,7 +213,7 @@ describe("Bedrock Converse route", () => { { type: "tool-input-delta", id: "tool_1", name: "lookup", text: '{"query"' }, { type: "tool-input-delta", id: "tool_1", name: "lookup", text: ':"weather"}' }, ]) - expect(response.events.at(-1)).toMatchObject({ type: "request-finish", reason: "tool-calls" }) + expect(response.events.at(-1)).toMatchObject({ type: "finish", reason: "tool-calls" }) }), ) diff --git a/packages/llm/test/provider/gemini.test.ts b/packages/llm/test/provider/gemini.test.ts index 80c32c58b..7e6bbc846 100644 --- a/packages/llm/test/provider/gemini.test.ts +++ b/packages/llm/test/provider/gemini.test.ts @@ -232,7 +232,7 @@ describe("Gemini route", () => { { type: "text-end", id: "text-0" }, { type: "step-finish", index: 0, reason: "stop", usage, providerMetadata: undefined }, { - type: "request-finish", + type: "finish", reason: "stop", usage, }, @@ -291,7 +291,7 @@ describe("Gemini route", () => { }, { type: "step-finish", index: 0, reason: "tool-calls", usage, providerMetadata: undefined }, { - type: "request-finish", + type: "finish", reason: "tool-calls", usage, }, @@ -325,7 +325,7 @@ describe("Gemini route", () => { { type: "tool-call", id: "tool_0", name: "lookup", input: { query: "weather" } }, { type: "tool-call", id: "tool_1", name: "lookup", input: { query: "news" } }, ]) - expect(response.events.at(-1)).toMatchObject({ type: "request-finish", reason: "tool-calls" }) + expect(response.events.at(-1)).toMatchObject({ type: "finish", reason: "tool-calls" }) }), ) @@ -344,10 +344,10 @@ describe("Gemini route", () => { ), ) - expect(length.events.map((event) => event.type)).toEqual(["step-start", "step-finish", "request-finish"]) - expect(length.events.at(-1)).toMatchObject({ type: "request-finish", reason: "length" }) - expect(filtered.events.map((event) => event.type)).toEqual(["step-start", "step-finish", "request-finish"]) - expect(filtered.events.at(-1)).toMatchObject({ type: "request-finish", reason: "content-filter" }) + expect(length.events.map((event) => event.type)).toEqual(["step-start", "step-finish", "finish"]) + expect(length.events.at(-1)).toMatchObject({ type: "finish", reason: "length" }) + expect(filtered.events.map((event) => event.type)).toEqual(["step-start", "step-finish", "finish"]) + expect(filtered.events.at(-1)).toMatchObject({ type: "finish", reason: "content-filter" }) }), ) diff --git a/packages/llm/test/provider/openai-chat.test.ts b/packages/llm/test/provider/openai-chat.test.ts index 115c58849..4303a69ff 100644 --- a/packages/llm/test/provider/openai-chat.test.ts +++ b/packages/llm/test/provider/openai-chat.test.ts @@ -249,7 +249,7 @@ describe("OpenAI Chat route", () => { { type: "text-end", id: "text-0" }, { type: "step-finish", index: 0, reason: "stop", usage, providerMetadata: undefined }, { - type: "request-finish", + type: "finish", reason: "stop", usage, }, @@ -288,7 +288,7 @@ describe("OpenAI Chat route", () => { providerMetadata: undefined, }, { type: "step-finish", index: 0, reason: "tool-calls", usage: undefined, providerMetadata: undefined }, - { type: "request-finish", reason: "tool-calls", usage: undefined }, + { type: "finish", reason: "tool-calls", usage: undefined }, ]) }), ) diff --git a/packages/llm/test/provider/openai-compatible-chat.test.ts b/packages/llm/test/provider/openai-compatible-chat.test.ts index 7759ff720..50aac4109 100644 --- a/packages/llm/test/provider/openai-compatible-chat.test.ts +++ b/packages/llm/test/provider/openai-compatible-chat.test.ts @@ -231,7 +231,7 @@ describe("OpenAI-compatible Chat route", () => { expect(response.text).toBe("Hello!") expect(response.usage).toMatchObject({ inputTokens: 5, outputTokens: 2, totalTokens: 7 }) - expect(response.events.at(-1)).toMatchObject({ type: "request-finish", reason: "stop" }) + expect(response.events.at(-1)).toMatchObject({ type: "finish", reason: "stop" }) }), ) }) diff --git a/packages/llm/test/provider/openai-responses.test.ts b/packages/llm/test/provider/openai-responses.test.ts index 8b4469f4e..63452f61b 100644 --- a/packages/llm/test/provider/openai-responses.test.ts +++ b/packages/llm/test/provider/openai-responses.test.ts @@ -366,7 +366,7 @@ describe("OpenAI Responses route", () => { usage, }, { - type: "request-finish", + type: "finish", reason: "stop", providerMetadata: { openai: { responseId: "resp_1", serviceTier: "default" } }, usage, @@ -447,7 +447,7 @@ describe("OpenAI Responses route", () => { }, { type: "step-finish", index: 0, reason: "tool-calls", usage, providerMetadata: undefined }, { - type: "request-finish", + type: "finish", reason: "tool-calls", providerMetadata: undefined, usage, diff --git a/packages/llm/test/recorded-scenarios.ts b/packages/llm/test/recorded-scenarios.ts index bdba8580f..3af7a7760 100644 --- a/packages/llm/test/recorded-scenarios.ts +++ b/packages/llm/test/recorded-scenarios.ts @@ -120,8 +120,8 @@ export const runWeatherToolLoop = (request: LLMRequest) => export const expectFinish = ( events: ReadonlyArray, - reason: Extract["reason"], -) => expect(events.at(-1)).toMatchObject({ type: "request-finish", reason }) + reason: Extract["reason"], +) => expect(events.at(-1)).toMatchObject({ type: "finish", reason }) export const expectWeatherToolCall = (response: LLMResponse) => expect(response.toolCalls).toMatchObject([ @@ -129,10 +129,12 @@ export const expectWeatherToolCall = (response: LLMResponse) => ]) export const expectWeatherToolLoop = (events: ReadonlyArray) => { - const finishes = events.filter(LLMEvent.is.requestFinish) - expect(finishes).toHaveLength(2) - expect(finishes[0]?.reason).toBe("tool-calls") - expect(finishes.at(-1)?.reason).toBe("stop") + const finishes = events.filter(LLMEvent.is.finish) + expect(finishes).toHaveLength(1) + expect(finishes[0]?.reason).toBe("stop") + + const stepFinishes = events.filter(LLMEvent.is.stepFinish) + expect(stepFinishes.map((event) => event.reason)).toEqual(["tool-calls", "stop"]) const toolCalls = events.filter(LLMEvent.is.toolCall) expect(toolCalls).toHaveLength(1) @@ -272,7 +274,7 @@ export const eventSummary = (events: ReadonlyArray) => { summary.push({ type: "tool-error", name: event.name, message: event.message }) continue } - if (event.type === "request-finish") { + if (event.type === "finish") { summary.push({ type: "finish", reason: event.reason, usage: usageSummary(event.usage) }) } } diff --git a/packages/llm/test/schema.test.ts b/packages/llm/test/schema.test.ts index 23bd9fd9b..01d6fadd9 100644 --- a/packages/llm/test/schema.test.ts +++ b/packages/llm/test/schema.test.ts @@ -44,6 +44,11 @@ describe("llm schema", () => { expect(() => Schema.decodeUnknownSync(LLMEvent)({ type: "bogus" })).toThrow() }) + test("finish constructors accept usage input", () => { + expect(LLMEvent.stepFinish({ index: 0, reason: "stop", usage: { inputTokens: 1 } }).usage).toBeInstanceOf(Usage) + expect(LLMEvent.finish({ reason: "stop", usage: { outputTokens: 2 } }).usage).toBeInstanceOf(Usage) + }) + test("content part tagged union exposes guards", () => { expect(ContentPart.guards.text({ type: "text", text: "hi" })).toBe(true) expect(ContentPart.guards.media({ type: "text", text: "hi" })).toBe(false) diff --git a/packages/llm/test/tool-runtime.test.ts b/packages/llm/test/tool-runtime.test.ts index 040a11fb6..573021c4c 100644 --- a/packages/llm/test/tool-runtime.test.ts +++ b/packages/llm/test/tool-runtime.test.ts @@ -4,7 +4,8 @@ import { GenerationOptions, LLM, LLMEvent, LLMRequest, LLMResponse, ToolChoice } import { LLMClient } from "../src/route" import * as AnthropicMessages from "../src/protocols/anthropic-messages" import * as OpenAIChat from "../src/protocols/openai-chat" -import { tool, ToolFailure } from "../src/tool" +import { tool, ToolFailure, type ToolExecuteContext } from "../src/tool" +import { ToolRuntime } from "../src/tool-runtime" import { it } from "./lib/effect" import * as TestToolRuntime from "./lib/tool-runtime" import { dynamicResponse, scriptedResponses } from "./lib/http" @@ -129,7 +130,7 @@ describe("LLMClient tools", () => { name: "get_weather", result: { type: "json", value: { temperature: 22, condition: "sunny" } }, }) - expect(events.at(-1)?.type).toBe("request-finish") + expect(events.at(-1)?.type).toBe("finish") expect(LLMResponse.text({ events })).toBe("It's sunny in Paris.") }), ) @@ -148,11 +149,40 @@ describe("LLMClient tools", () => { ), ) - expect(events.filter(LLMEvent.is.requestFinish)).toHaveLength(1) + expect(events.filter(LLMEvent.is.finish)).toHaveLength(1) expect(events.find(LLMEvent.is.toolResult)).toMatchObject({ type: "tool-result", id: "call_1" }) }), ) + it.effect("passes tool call context to execute", () => + Effect.gen(function* () { + let context: ToolExecuteContext | undefined + const contextual = tool({ + description: "Capture tool context.", + parameters: Schema.Struct({ value: Schema.String }), + success: Schema.Struct({ ok: Schema.Boolean }), + execute: (_params, ctx) => + Effect.sync(() => { + context = ctx + return { ok: true } + }), + }) + const events = Array.from( + yield* TestToolRuntime.runTools({ request: baseRequest, tools: { contextual } }).pipe( + Stream.runCollect, + Effect.provide( + scriptedResponses([ + sseEvents(toolCallChunk("call_ctx", "contextual", '{"value":"x"}'), finishChunk("tool_calls")), + ]), + ), + ), + ) + + expect(events.some(LLMEvent.is.toolResult)).toBe(true) + expect(context).toEqual({ id: "call_ctx", name: "contextual" }) + }), + ) + it.effect("can expose tool schemas without executing tool calls", () => Effect.gen(function* () { const layer = scriptedResponses([ @@ -319,7 +349,7 @@ describe("LLMClient tools", () => { "text-delta", "text-end", "step-finish", - "request-finish", + "finish", ]) expect(LLMResponse.text({ events })).toBe("Done.") }), @@ -343,7 +373,57 @@ describe("LLMClient tools", () => { ), ) - expect(events.filter(LLMEvent.is.requestFinish)).toHaveLength(2) + expect(events.filter(LLMEvent.is.finish)).toHaveLength(1) + expect(events.filter(LLMEvent.is.stepStart).map((event) => event.index)).toEqual([0, 1]) + expect(events.filter(LLMEvent.is.stepFinish).map((event) => event.index)).toEqual([0, 1]) + }), + ) + + it.effect("emits one final finish with aggregate usage", () => + Effect.gen(function* () { + let calls = 0 + const events = Array.from( + yield* ToolRuntime.stream({ + request: baseRequest, + tools: { get_weather }, + stopWhen: ToolRuntime.stepCountIs(2), + stream: () => + Stream.fromIterable( + calls++ === 0 + ? [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.toolCall({ id: "call_1", name: "get_weather", input: { city: "Paris" } }), + LLMEvent.stepFinish({ + index: 0, + reason: "tool-calls", + usage: { inputTokens: 1, outputTokens: 2, totalTokens: 3 }, + }), + LLMEvent.finish({ + reason: "tool-calls", + usage: { inputTokens: 1, outputTokens: 2, totalTokens: 3 }, + }), + ] + : [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.textDelta({ id: "text_1", text: "Done." }), + LLMEvent.stepFinish({ + index: 0, + reason: "stop", + usage: { inputTokens: 4, outputTokens: 5, totalTokens: 9 }, + }), + LLMEvent.finish({ reason: "stop", usage: { inputTokens: 4, outputTokens: 5, totalTokens: 9 } }), + ], + ), + }).pipe(Stream.runCollect), + ) + + expect(events.filter(LLMEvent.is.stepFinish).map((event) => event.index)).toEqual([0, 1]) + expect(events.filter(LLMEvent.is.finish)).toHaveLength(1) + expect(events.find(LLMEvent.is.finish)?.usage).toMatchObject({ + inputTokens: 5, + outputTokens: 7, + totalTokens: 12, + }) }), ) @@ -362,7 +442,7 @@ describe("LLMClient tools", () => { }).pipe(Stream.runCollect, Effect.provide(layer)), ) - expect(events.filter(LLMEvent.is.requestFinish)).toHaveLength(1) + expect(events.filter(LLMEvent.is.finish)).toHaveLength(1) expect(events.find(LLMEvent.is.toolResult)).toMatchObject({ type: "tool-result", id: "call_1" }) }), ) From af4ab017cbf603be0778071a152f818d4836ec0b Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:30:35 -0400 Subject: [PATCH 139/378] test(session): migrate structured output integration test (#27143) --- .../structured-output-integration.test.ts | 397 ++++++++---------- 1 file changed, 183 insertions(+), 214 deletions(-) diff --git a/packages/opencode/test/session/structured-output-integration.test.ts b/packages/opencode/test/session/structured-output-integration.test.ts index da2ffb793..125c63c0f 100644 --- a/packages/opencode/test/session/structured-output-integration.test.ts +++ b/packages/opencode/test/session/structured-output-integration.test.ts @@ -1,250 +1,219 @@ import { describe, expect, test } from "bun:test" -import path from "path" import { Effect, Layer } from "effect" import { Session } from "@/session/session" import { SessionPrompt } from "../../src/session/prompt" import * as Log from "@opencode-ai/core/util/log" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { MessageV2 } from "../../src/session/message-v2" +import { testEffect } from "../lib/effect" -const projectRoot = path.join(__dirname, "../..") void Log.init({ print: false }) // Skip tests if no API key is available const hasApiKey = !!process.env.ANTHROPIC_API_KEY - -// Helper to run test within Instance context -async function withInstance(fn: () => Promise): Promise { - return WithInstance.provide({ - directory: projectRoot, - fn, - }) -} - -function run(fx: Effect.Effect) { - return Effect.runPromise( - fx.pipe(Effect.scoped, Effect.provide(Layer.mergeAll(SessionPrompt.defaultLayer, Session.defaultLayer))), - ) -} +const it = testEffect(Layer.mergeAll(SessionPrompt.defaultLayer, Session.defaultLayer)) +const live = hasApiKey ? it.instance : it.instance.skip describe("StructuredOutput Integration", () => { - test.skipIf(!hasApiKey)( + live( "produces structured output with simple schema", - async () => { - await withInstance(() => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ title: "Structured Output Test" }) - - const result = yield* prompt.prompt({ - sessionID: session.id, - parts: [ - { - type: "text", - text: "What is 2 + 2? Provide a simple answer.", - }, - ], - format: { - type: "json_schema", - schema: { - type: "object", - properties: { - answer: { type: "number", description: "The numerical answer" }, - explanation: { type: "string", description: "Brief explanation" }, - }, - required: ["answer"], - }, - retryCount: 0, + () => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ title: "Structured Output Test" }) + + const result = yield* prompt.prompt({ + sessionID: session.id, + parts: [ + { + type: "text", + text: "What is 2 + 2? Provide a simple answer.", + }, + ], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + answer: { type: "number", description: "The numerical answer" }, + explanation: { type: "string", description: "Brief explanation" }, }, - }) - - // Verify structured output was captured (only on assistant messages) - expect(result.info.role).toBe("assistant") - if (result.info.role === "assistant") { - expect(result.info.structured).toBeDefined() - expect(typeof result.info.structured).toBe("object") - - const output = result.info.structured as any - expect(output.answer).toBe(4) - - // Verify no error was set - expect(result.info.error).toBeUndefined() - } - - // Clean up - // Note: Not removing session to avoid race with background SessionSummary.summarize - }), - ), - ) - }, + required: ["answer"], + }, + retryCount: 0, + }, + }) + + // Verify structured output was captured (only on assistant messages) + expect(result.info.role).toBe("assistant") + if (result.info.role === "assistant") { + expect(result.info.structured).toBeDefined() + expect(typeof result.info.structured).toBe("object") + + const output = result.info.structured as any + expect(output.answer).toBe(4) + + // Verify no error was set + expect(result.info.error).toBeUndefined() + } + + // Clean up + // Note: Not removing session to avoid race with background SessionSummary.summarize + }), + { git: true }, 60000, ) - test.skipIf(!hasApiKey)( + live( "produces structured output with nested objects", - async () => { - await withInstance(() => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ title: "Nested Schema Test" }) - - const result = yield* prompt.prompt({ - sessionID: session.id, - parts: [ - { - type: "text", - text: "Tell me about Anthropic company in a structured format.", - }, - ], - format: { - type: "json_schema", - schema: { + () => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ title: "Nested Schema Test" }) + + const result = yield* prompt.prompt({ + sessionID: session.id, + parts: [ + { + type: "text", + text: "Tell me about Anthropic company in a structured format.", + }, + ], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + company: { type: "object", properties: { - company: { - type: "object", - properties: { - name: { type: "string" }, - founded: { type: "number" }, - }, - required: ["name", "founded"], - }, - products: { - type: "array", - items: { type: "string" }, - }, + name: { type: "string" }, + founded: { type: "number" }, }, - required: ["company"], + required: ["name", "founded"], + }, + products: { + type: "array", + items: { type: "string" }, }, - retryCount: 0, }, - }) - - // Verify structured output was captured (only on assistant messages) - expect(result.info.role).toBe("assistant") - if (result.info.role === "assistant") { - expect(result.info.structured).toBeDefined() - const output = result.info.structured as any - - expect(output.company).toBeDefined() - expect(output.company.name).toBe("Anthropic") - expect(typeof output.company.founded).toBe("number") - - if (output.products) { - expect(Array.isArray(output.products)).toBe(true) - } - - // Verify no error was set - expect(result.info.error).toBeUndefined() - } - - // Clean up - // Note: Not removing session to avoid race with background SessionSummary.summarize - }), - ), - ) - }, + required: ["company"], + }, + retryCount: 0, + }, + }) + + // Verify structured output was captured (only on assistant messages) + expect(result.info.role).toBe("assistant") + if (result.info.role === "assistant") { + expect(result.info.structured).toBeDefined() + const output = result.info.structured as any + + expect(output.company).toBeDefined() + expect(output.company.name).toBe("Anthropic") + expect(typeof output.company.founded).toBe("number") + + if (output.products) { + expect(Array.isArray(output.products)).toBe(true) + } + + // Verify no error was set + expect(result.info.error).toBeUndefined() + } + + // Clean up + // Note: Not removing session to avoid race with background SessionSummary.summarize + }), + { git: true }, 60000, ) - test.skipIf(!hasApiKey)( + live( "works with text outputFormat (default)", - async () => { - await withInstance(() => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ title: "Text Output Test" }) - - const result = yield* prompt.prompt({ - sessionID: session.id, - parts: [ - { - type: "text", - text: "Say hello.", - }, - ], - format: { - type: "text", - }, - }) - - // Verify no structured output (text mode) and no error - expect(result.info.role).toBe("assistant") - if (result.info.role === "assistant") { - expect(result.info.structured).toBeUndefined() - expect(result.info.error).toBeUndefined() - } - - // Verify we got a response with parts - expect(result.parts.length).toBeGreaterThan(0) - - // Clean up - // Note: Not removing session to avoid race with background SessionSummary.summarize - }), - ), - ) - }, + () => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ title: "Text Output Test" }) + + const result = yield* prompt.prompt({ + sessionID: session.id, + parts: [ + { + type: "text", + text: "Say hello.", + }, + ], + format: { + type: "text", + }, + }) + + // Verify no structured output (text mode) and no error + expect(result.info.role).toBe("assistant") + if (result.info.role === "assistant") { + expect(result.info.structured).toBeUndefined() + expect(result.info.error).toBeUndefined() + } + + // Verify we got a response with parts + expect(result.parts.length).toBeGreaterThan(0) + + // Clean up + // Note: Not removing session to avoid race with background SessionSummary.summarize + }), + { git: true }, 60000, ) - test.skipIf(!hasApiKey)( + live( "stores outputFormat on user message", - async () => { - await withInstance(() => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ title: "OutputFormat Storage Test" }) - - yield* prompt.prompt({ - sessionID: session.id, - parts: [ - { - type: "text", - text: "What is 1 + 1?", - }, - ], - format: { - type: "json_schema", - schema: { - type: "object", - properties: { - result: { type: "number" }, - }, - required: ["result"], - }, - retryCount: 3, + () => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ title: "OutputFormat Storage Test" }) + + yield* prompt.prompt({ + sessionID: session.id, + parts: [ + { + type: "text", + text: "What is 1 + 1?", + }, + ], + format: { + type: "json_schema", + schema: { + type: "object", + properties: { + result: { type: "number" }, }, - }) - - // Get all messages from session - const messages = yield* sessions.messages({ sessionID: session.id }) - const userMessage = messages.find((m) => m.info.role === "user") - - // Verify outputFormat was stored on user message - expect(userMessage).toBeDefined() - if (userMessage?.info.role === "user") { - expect(userMessage.info.format).toBeDefined() - expect(userMessage.info.format?.type).toBe("json_schema") - if (userMessage.info.format?.type === "json_schema") { - expect(userMessage.info.format.retryCount).toBe(3) - } - } - - // Clean up - // Note: Not removing session to avoid race with background SessionSummary.summarize - }), - ), - ) - }, + required: ["result"], + }, + retryCount: 3, + }, + }) + + // Get all messages from session + const messages = yield* sessions.messages({ sessionID: session.id }) + const userMessage = messages.find((m) => m.info.role === "user") + + // Verify outputFormat was stored on user message + expect(userMessage).toBeDefined() + if (userMessage?.info.role === "user") { + expect(userMessage.info.format).toBeDefined() + expect(userMessage.info.format?.type).toBe("json_schema") + if (userMessage.info.format?.type === "json_schema") { + expect(userMessage.info.format.retryCount).toBe(3) + } + } + + // Clean up + // Note: Not removing session to avoid race with background SessionSummary.summarize + }), + { git: true }, 60000, ) From dc9d6a08cbeac02ea02197eb85f6aa409bffa0c4 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:31:38 -0400 Subject: [PATCH 140/378] test: migrate agent color config tests (#27139) --- .../opencode/test/config/agent-color.test.ts | 68 ++++++++----------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/packages/opencode/test/config/agent-color.test.ts b/packages/opencode/test/config/agent-color.test.ts index 49509156a..369b3a1fd 100644 --- a/packages/opencode/test/config/agent-color.test.ts +++ b/packages/opencode/test/config/agent-color.test.ts @@ -1,58 +1,50 @@ import { test, expect } from "bun:test" import { Effect, Layer } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import path from "path" -import { provideInstance, tmpdirScoped } from "../fixture/fixture" import { Config } from "@/config/config" import { Agent as AgentSvc } from "../../src/agent/agent" import { Color } from "@/util/color" -import { AppRuntime } from "../../src/effect/app-runtime" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(AgentSvc.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect(Layer.mergeAll(Config.defaultLayer, AgentSvc.defaultLayer, CrossSpawnSpawner.defaultLayer)) -const writeConfig = (dir: string, agent: Config.Info["agent"]) => - Effect.promise(() => - Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - agent, - }), - ), - ) - -it.live("agent color parsed from project config", () => - Effect.gen(function* () { - const dir = yield* tmpdirScoped() - yield* writeConfig(dir, { - build: { color: "#FFA500" }, - plan: { color: "primary" }, - }) - - yield* Effect.gen(function* () { - const cfg = yield* Effect.promise(() => AppRuntime.runPromise(Config.Service.use((svc) => svc.get()))) +it.instance( + "agent color parsed from project config", + () => + Effect.gen(function* () { + const cfg = yield* Config.Service.use((svc) => svc.get()) expect(cfg.agent?.["build"]?.color).toBe("#FFA500") expect(cfg.agent?.["plan"]?.color).toBe("primary") - }).pipe(provideInstance(dir)) - }), + }), + { + git: true, + config: { + agent: { + build: { color: "#FFA500" }, + plan: { color: "primary" }, + }, + }, + }, ) -it.live("Agent.get includes color from config", () => - Effect.gen(function* () { - const dir = yield* tmpdirScoped() - yield* writeConfig(dir, { - plan: { color: "#A855F7" }, - build: { color: "accent" }, - }) - - yield* Effect.gen(function* () { +it.instance( + "Agent.get includes color from config", + () => + Effect.gen(function* () { const plan = yield* AgentSvc.Service.use((svc) => svc.get("plan")) expect(plan?.color).toBe("#A855F7") const build = yield* AgentSvc.Service.use((svc) => svc.get("build")) expect(build?.color).toBe("accent") - }).pipe(provideInstance(dir)) - }), + }), + { + git: true, + config: { + agent: { + plan: { color: "#A855F7" }, + build: { color: "accent" }, + }, + }, + }, ) test("Color.hexToAnsiBold converts valid hex to ANSI", () => { From 2017dc165c7c2bf07c3578d1405caf1c82d66598 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:32:23 -0400 Subject: [PATCH 141/378] test: migrate negative tokens regression to Effect runner (#27141) --- .../server/negative-tokens-regression.test.ts | 124 ++++++++---------- 1 file changed, 54 insertions(+), 70 deletions(-) diff --git a/packages/opencode/test/server/negative-tokens-regression.test.ts b/packages/opencode/test/server/negative-tokens-regression.test.ts index 77ad1bc27..290023ead 100644 --- a/packages/opencode/test/server/negative-tokens-regression.test.ts +++ b/packages/opencode/test/server/negative-tokens-regression.test.ts @@ -5,11 +5,10 @@ // negative. The pre-fix `safe()` clamp only guarded against non-finite. The // strict `NonNegativeInt` schema then made every load of the message list // fail to encode, killing Desktop boot for every user with such a row. -import { afterEach, describe, expect } from "bun:test" +import { describe, expect } from "bun:test" import { Effect } from "effect" import { eq } from "drizzle-orm" import { ModelID, ProviderID } from "../../src/provider/schema" -import { WithInstance } from "../../src/project/with-instance" import { Server } from "../../src/server/server" import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session" import { Session } from "@/session/session" @@ -17,81 +16,66 @@ import { MessageID, PartID } from "../../src/session/schema" import * as Database from "@/storage/db" import { PartTable } from "@/session/session.sql" import { resetDatabase } from "../fixture/db" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" -import { it } from "../lib/effect" +import { TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" -afterEach(async () => { - await disposeAllInstances() - await resetDatabase() -}) +const it = testEffect(Session.defaultLayer) -function seedNegativeTokenSession(directory: string) { - return Effect.promise(async () => - WithInstance.provide({ - directory, - fn: () => - Effect.runPromise( - Effect.gen(function* () { - const session = yield* Session.Service - const info = yield* session.create({}) - const message = yield* session.updateMessage({ - id: MessageID.ascending(), - role: "user", - sessionID: info.id, - agent: "build", - model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, - time: { created: Date.now() }, - }) - const partID = PartID.ascending() - yield* session.updatePart({ - id: partID, - sessionID: info.id, - messageID: message.id, - type: "step-finish", - reason: "stop", - cost: 0, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - }) +function seedNegativeTokenSession() { + return Effect.gen(function* () { + const session = yield* Session.Service + const info = yield* session.create({}) + const message = yield* session.updateMessage({ + id: MessageID.ascending(), + role: "user", + sessionID: info.id, + agent: "build", + model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, + time: { created: Date.now() }, + }) + const partID = PartID.ascending() + yield* session.updatePart({ + id: partID, + sessionID: info.id, + messageID: message.id, + type: "step-finish", + reason: "stop", + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + }) - // Bypass the schema with a direct SQL update to install the - // negative `output` value we want to test loading. - Database.use((db) => - db - .update(PartTable) - .set({ - data: { - type: "step-finish", - reason: "stop", - cost: 0, - tokens: { input: 0, output: -42, reasoning: 0, cache: { read: 0, write: 0 } }, - } as never, - }) - .where(eq(PartTable.id, partID)) - .run(), - ) + // Bypass the schema with a direct SQL update to install the + // negative `output` value we want to test loading. + Database.use((db) => + db + .update(PartTable) + .set({ + data: { + type: "step-finish", + reason: "stop", + cost: 0, + tokens: { input: 0, output: -42, reasoning: 0, cache: { read: 0, write: 0 } }, + } as never, + }) + .where(eq(PartTable.id, partID)) + .run(), + ) - return info.id - }).pipe(Effect.provide(Session.defaultLayer)), - ), - }), - ) + return info.id + }) } describe("messages endpoint tolerates legacy negative token counts", () => { - it.live( + it.instance( "returns 200 even when a step-finish part has tokens.output < 0", - Effect.acquireRelease( - Effect.promise(() => tmpdir({ config: { formatter: false, lsp: false } })), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ).pipe( - Effect.flatMap((tmp) => - Effect.gen(function* () { - const sessionID = yield* seedNegativeTokenSession(tmp.path) - const url = `${SessionPaths.messages.replace(":sessionID", sessionID)}?limit=80&directory=${encodeURIComponent(tmp.path)}` - const res = yield* Effect.promise(async () => Server.Default().app.request(url)) - expect(res.status, "messages endpoint 400'd on legacy negative tokens").not.toBe(400) - }), - ), - ), + Effect.gen(function* () { + yield* Effect.addFinalizer(() => Effect.promise(() => resetDatabase())) + const test = yield* TestInstance + const sessionID = yield* seedNegativeTokenSession() + const url = `${SessionPaths.messages.replace(":sessionID", sessionID)}?limit=80&directory=${encodeURIComponent(test.directory)}` + const res = yield* Effect.promise(async () => Server.Default().app.request(url)) + expect(res.status, "messages endpoint 400'd on legacy negative tokens").not.toBe(400) + }), + { git: true, config: { formatter: false, lsp: false } }, ) }) From ca28dd02ec8eb98a7ad8afea83fdbc3105c48453 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 12 May 2026 15:33:16 -0500 Subject: [PATCH 142/378] fix(compaction): restore tail turns after summarization (#27145) --- packages/opencode/src/config/config.ts | 4 +- packages/opencode/src/session/compaction.ts | 60 ++++++++----------- packages/opencode/src/session/message-v2.ts | 42 ++++++++++++- .../opencode/test/session/compaction.test.ts | 50 +++++++--------- .../test/session/messages-pagination.test.ts | 16 ++--- 5 files changed, 95 insertions(+), 77 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index d00c97f46..545e48e64 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -261,10 +261,10 @@ export const Info = Schema.Struct({ }), tail_turns: Schema.optional(NonNegativeInt).annotate({ description: - "Number of recent user turns, including their following assistant/tool responses, to serialize into the compaction summary (default: 2)", + "Number of recent user turns, including their following assistant/tool responses, to keep verbatim during compaction (default: 2)", }), preserve_recent_tokens: Schema.optional(NonNegativeInt).annotate({ - description: "Maximum number of tokens from recent turns to serialize into the compaction summary", + description: "Maximum number of tokens from recent turns to preserve verbatim after compaction", }), reserved: Schema.optional(NonNegativeInt).annotate({ description: "Token buffer for compaction. Leaves enough window to avoid overflow during compaction.", diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 3ca4f074f..4eafbdf74 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -79,10 +79,12 @@ Rules: type Turn = { start: number end: number + id: MessageID } type Tail = { start: number + id: MessageID } type CompletedCompaction = { @@ -119,41 +121,19 @@ function completedCompactions(messages: MessageV2.WithParts[]) { }) } -function buildPrompt(input: { previousSummary?: string; context: string[]; tail?: string }) { - const source = input.tail - ? "the conversation history above and the serialized recent conversation tail below" - : "the conversation history above" +function buildPrompt(input: { previousSummary?: string; context: string[] }) { const anchor = input.previousSummary ? [ - `Update the anchored summary below using ${source}.`, + "Update the anchored summary below using the conversation history above.", "Preserve still-true details, remove stale details, and merge in the new facts.", "", input.previousSummary, "", ].join("\n") - : `Create a new anchored summary from ${source}.` - const tail = input.tail - ? [ - "Fold this serialized recent conversation tail into the summary; it is not provider message history.", - "", - input.tail, - "", - ].join("\n") - : undefined - return [anchor, ...(tail ? [tail] : []), SUMMARY_TEMPLATE, ...input.context].join("\n\n") + : "Create a new anchored summary from the conversation history above." + return [anchor, SUMMARY_TEMPLATE, ...input.context].join("\n\n") } -const serialize = Effect.fn("SessionCompaction.serialize")(function* (input: { - messages: MessageV2.WithParts[] - model: Provider.Model -}) { - const messages = yield* MessageV2.toModelMessagesEffect(input.messages, input.model, { - stripMedia: true, - toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS, - }) - return messages.length ? JSON.stringify(messages, null, 2) : undefined -}) - function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model }) { return ( input.cfg.compaction?.preserve_recent_tokens ?? @@ -170,6 +150,7 @@ function turns(messages: MessageV2.WithParts[]) { result.push({ start: i, end: messages.length, + id: msg.info.id, }) } for (let i = 0; i < result.length - 1; i++) { @@ -196,6 +177,7 @@ function splitTurn(input: { if (size > input.budget) continue return { start, + id: input.messages[start]!.info.id, } satisfies Tail } return undefined @@ -262,7 +244,8 @@ export const layer: Layer.Layer< messages: MessageV2.WithParts[] model: Provider.Model }) { - return Token.estimate((yield* serialize(input)) ?? "") + const msgs = yield* MessageV2.toModelMessagesEffect(input.messages, input.model) + return Token.estimate(JSON.stringify(msgs)) }) const select = Effect.fn("SessionCompaction.select")(function* (input: { @@ -271,10 +254,10 @@ export const layer: Layer.Layer< model: Provider.Model }) { const limit = input.cfg.compaction?.tail_turns ?? DEFAULT_TAIL_TURNS - if (limit <= 0) return { head: input.messages, tail: [] } + if (limit <= 0) return { head: input.messages, tail_start_id: undefined } const budget = preserveRecentBudget({ cfg: input.cfg, model: input.model }) const all = turns(input.messages) - if (!all.length) return { head: input.messages, tail: [] } + if (!all.length) return { head: input.messages, tail_start_id: undefined } const recent = all.slice(-limit) const sizes = yield* Effect.forEach( recent, @@ -293,7 +276,7 @@ export const layer: Layer.Layer< const size = sizes[i] if (total + size <= budget) { total += size - keep = { start: turn.start } + keep = { start: turn.start, id: turn.id } continue } const remaining = budget - total @@ -309,10 +292,10 @@ export const layer: Layer.Layer< break } - if (!keep) return { head: input.messages, tail: [] } + if (!keep || keep.start === 0) return { head: input.messages, tail_start_id: undefined } return { head: input.messages.slice(0, keep.start), - tail: input.messages.slice(keep.start), + tail_start_id: keep.id, } }) @@ -423,10 +406,7 @@ export const layer: Layer.Layer< { sessionID: input.sessionID }, { context: [], prompt: undefined }, ) - const tailMessages = structuredClone(selected.tail) - yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: tailMessages }) - const tail = yield* serialize({ messages: tailMessages, model }) - const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context, tail }) + const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context }) const msgs = structuredClone(selected.head) yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, { @@ -493,6 +473,13 @@ export const layer: Layer.Layer< return "stop" } + if (compactionPart && selected.tail_start_id && compactionPart.tail_start_id !== selected.tail_start_id) { + yield* session.updatePart({ + ...compactionPart, + tail_start_id: selected.tail_start_id, + }) + } + if (result === "continue" && input.auto) { if (replay) { const original = replay.info @@ -588,6 +575,7 @@ export const layer: Layer.Layer< sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), text: summary ?? "", + include: selected.tail_start_id, }) } yield* bus.publish(Event.Compacted, { sessionID: input.sessionID }) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 626261d0f..e6ee40e95 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -772,13 +772,12 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* ( return part.metadata?.anthropic?.signature != null }) for (const part of msg.parts) { - if (msg.info.summary && part.type !== "text") continue if (part.type === "text") { const text = part.text === "" && hasSignedReasoning ? " " : part.text assistantMessage.parts.push({ type: "text", text, - ...(differentModel || msg.info.summary ? {} : { providerMetadata: part.metadata }), + ...(differentModel ? {} : { providerMetadata: part.metadata }), }) } if (part.type === "step-start") @@ -1004,16 +1003,53 @@ export function get(input: { sessionID: SessionID; messageID: MessageID }): With export function filterCompacted(msgs: Iterable) { const result = [] as WithParts[] const completed = new Set() + let retain: MessageID | undefined for (const msg of msgs) { result.push(msg) + if (retain) { + if (msg.info.id === retain) break + continue + } if (msg.info.role === "user" && completed.has(msg.info.id)) { - if (msg.parts.some((item): item is CompactionPart => item.type === "compaction")) break + const part = msg.parts.find((item): item is CompactionPart => item.type === "compaction") + if (!part) continue + if (!part.tail_start_id) break + retain = part.tail_start_id + if (msg.info.id === retain) break continue } + if (msg.info.role === "user" && completed.has(msg.info.id) && msg.parts.some((part) => part.type === "compaction")) + break if (msg.info.role === "assistant" && msg.info.summary && msg.info.finish && !msg.info.error) completed.add(msg.info.parentID) } result.reverse() + const compactionIndex = result.findLastIndex( + (msg) => + msg.info.role === "user" && + msg.parts.some((item): item is CompactionPart => item.type === "compaction" && item.tail_start_id !== undefined), + ) + const compaction = result[compactionIndex] + const part = compaction?.parts.find( + (item): item is CompactionPart => item.type === "compaction" && item.tail_start_id !== undefined, + ) + const summaryIndex = compaction + ? result.findIndex( + (msg, index) => + index > compactionIndex && + msg.info.role === "assistant" && + msg.info.summary && + msg.info.parentID === compaction.info.id, + ) + : -1 + const tailIndex = part?.tail_start_id ? result.findIndex((msg) => msg.info.id === part.tail_start_id) : -1 + if (tailIndex >= 0 && tailIndex < compactionIndex && summaryIndex > compactionIndex) { + return [ + ...result.slice(compactionIndex, summaryIndex + 1), + ...result.slice(tailIndex, compactionIndex), + ...result.slice(summaryIndex + 1), + ] + } return result } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index c7f349d5c..1d329699f 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -926,12 +926,12 @@ describe("session.compaction.process", () => { ) itCompaction.instance( - "does not persist tail_start_id for serialized recent turns", + "persists tail_start_id for retained recent turns", Effect.gen(function* () { const ssn = yield* SessionNs.Service const session = yield* ssn.create({}) yield* createUserMessage(session.id, "first") - yield* createUserMessage(session.id, "second") + const keep = yield* createUserMessage(session.id, "second") yield* createUserMessage(session.id, "third") yield* createSummaryCompaction(session.id) @@ -947,18 +947,18 @@ describe("session.compaction.process", () => { const part = yield* readCompactionPart(session.id) expect(part?.type).toBe("compaction") - expect(part?.tail_start_id).toBeUndefined() + expect(part?.tail_start_id).toBe(keep.id) }).pipe(withCompaction({ config: cfg({ tail_turns: 2, preserve_recent_tokens: 10_000 }) })), ) itCompaction.instance( - "does not persist tail_start_id when shrinking serialized tail", + "shrinks retained tail to fit preserve token budget", Effect.gen(function* () { const ssn = yield* SessionNs.Service const session = yield* ssn.create({}) yield* createUserMessage(session.id, "first") yield* createUserMessage(session.id, "x".repeat(2_000)) - yield* createUserMessage(session.id, "tiny") + const keep = yield* createUserMessage(session.id, "tiny") yield* createSummaryCompaction(session.id) const msgs = yield* ssn.messages({ sessionID: session.id }) @@ -973,7 +973,7 @@ describe("session.compaction.process", () => { const part = yield* readCompactionPart(session.id) expect(part?.type).toBe("compaction") - expect(part?.tail_start_id).toBeUndefined() + expect(part?.tail_start_id).toBe(keep.id) }).pipe(withCompaction({ config: cfg({ tail_turns: 2, preserve_recent_tokens: 100 }) })), ) @@ -1005,7 +1005,7 @@ describe("session.compaction.process", () => { ) itCompaction.instance( - "serializes retained tail media as text in the summary input", + "falls back to full summary when retained tail media exceeds preserve token budget", () => { const stub = llm() let captured = "" @@ -1078,16 +1078,15 @@ describe("session.compaction.process", () => { const part = yield* readCompactionPart(session.id) expect(part?.type).toBe("compaction") - expect(part?.tail_start_id).toBeUndefined() + expect(part?.tail_start_id).toBe(keep.id) expect(captured).toContain("zzzz") - expect(captured).toContain("keep tail") + expect(captured).not.toContain("keep tail") const filtered = MessageV2.filterCompacted(MessageV2.stream(session.id)) - expect(filtered.map((msg) => msg.info.id)).toEqual([parent!, expect.any(String)]) + expect(filtered.map((msg) => msg.info.id).slice(0, 3)).toEqual([parent!, expect.any(String), keep.id]) expect(filtered[1]?.info.role).toBe("assistant") expect(filtered[1]?.info.role === "assistant" ? filtered[1].info.summary : false).toBe(true) expect(filtered.map((msg) => msg.info.id)).not.toContain(large.id) - expect(filtered.map((msg) => msg.info.id)).not.toContain(keep.id) }).pipe(withCompaction({ llm: stub.layer, config: cfg({ tail_turns: 1, preserve_recent_tokens: 100 }) })) }, { git: true }, @@ -1354,13 +1353,13 @@ describe("session.compaction.process", () => { ) itCompaction.instance( - "summarizes the head while serializing recent tail into summary input", + "summarizes only the head while keeping recent tail out of summary input", () => { const stub = llm() - let captured: LLM.StreamInput["messages"] = [] + let captured = "" stub.push( reply("summary", (input) => { - captured = input.messages + captured = JSON.stringify(input.messages) }), ) return Effect.gen(function* () { @@ -1381,15 +1380,10 @@ describe("session.compaction.process", () => { auto: false, }) - const head = JSON.stringify(captured.slice(0, -1)) - const prompt = JSON.stringify(captured.at(-1)) - expect(head).toContain("older context") - expect(head).not.toContain("keep this turn") - expect(head).not.toContain("and this one too") - expect(prompt).toContain("keep this turn") - expect(prompt).toContain("and this one too") - expect(prompt).toContain("recent-conversation-tail") - expect(prompt).not.toContain("What did we do so far?") + expect(captured).toContain("older context") + expect(captured).not.toContain("keep this turn") + expect(captured).not.toContain("and this one too") + expect(captured).not.toContain("What did we do so far?") }).pipe(withCompaction({ llm: stub.layer })) }, { git: true }, @@ -1437,7 +1431,7 @@ describe("session.compaction.process", () => { { git: true }, ) - itCompaction.instance("does not replay recent pre-compaction turns across repeated compactions", () => { + itCompaction.instance("keeps recent pre-compaction turns across repeated compactions", () => { const stub = llm() stub.push(reply("summary one")) stub.push(reply("summary two")) @@ -1468,8 +1462,8 @@ describe("session.compaction.process", () => { expect(ids).not.toContain(u1.id) expect(ids).not.toContain(u2.id) - expect(ids).not.toContain(u3.id) - expect(ids).not.toContain(u4.id) + expect(ids).toContain(u3.id) + expect(ids).toContain(u4.id) expect(filtered.some((msg) => msg.info.role === "assistant" && msg.info.summary)).toBe(true) expect( filtered.some((msg) => msg.info.role === "user" && msg.parts.some((part) => part.type === "compaction")), @@ -1478,7 +1472,7 @@ describe("session.compaction.process", () => { }) itCompaction.instance( - "ignores previous summaries when sizing the serialized tail", + "ignores previous summaries when sizing the retained tail", Effect.gen(function* () { const ssn = yield* SessionNs.Service const test = yield* TestInstance @@ -1517,7 +1511,7 @@ describe("session.compaction.process", () => { const part = yield* readCompactionPart(session.id) expect(part?.type).toBe("compaction") - expect(part?.tail_start_id).toBeUndefined() + expect(part?.tail_start_id).toBe(keep.id) }).pipe(withCompaction({ config: cfg({ tail_turns: 2, preserve_recent_tokens: 500 }) })), ) }) diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index e1714a901..09e8d7b42 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -650,7 +650,7 @@ describe("MessageV2.filterCompacted", () => { ), ) - it.instance("ignores original tail when compaction stores tail_start_id", () => + it.instance("retains original tail when compaction stores tail_start_id", () => withSession(({ session, sessionID }) => Effect.gen(function* () { const u1 = yield* addUser(sessionID, "first") @@ -696,12 +696,12 @@ describe("MessageV2.filterCompacted", () => { const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) + expect(result.map((item) => item.info.id)).toEqual([c1, s1, u2, a2, u3, a3]) }), ), ) - it.instance("fork keeps legacy tail_start_id without replaying the tail", () => + it.instance("fork remaps compaction tail_start_id for filterCompacted", () => Effect.gen(function* () { const session = yield* SessionNs.Service const created = yield* session.create({}) @@ -748,7 +748,7 @@ describe("MessageV2.filterCompacted", () => { }) const parentFiltered = MessageV2.filterCompacted(MessageV2.stream(created.id)) - expect(parentFiltered.map((item) => item.info.id)).toEqual([c1, s1, u3, a3]) + expect(parentFiltered.map((item) => item.info.id)).toEqual([c1, s1, u2, a2, u3, a3]) const forked = yield* session.fork({ sessionID: created.id }) const childFiltered = MessageV2.filterCompacted(MessageV2.stream(forked.id)) @@ -758,14 +758,14 @@ describe("MessageV2.filterCompacted", () => { expect(tailPart?.type).toBe("compaction") if (!tailPart || tailPart.type !== "compaction") throw new Error("Expected forked compaction part") expect(tailPart.tail_start_id).toBeDefined() - expect(childFiltered.some((m) => m.info.id === tailPart.tail_start_id)).toBe(false) + expect(childFiltered.some((m) => m.info.id === tailPart.tail_start_id)).toBe(true) yield* session.remove(forked.id) yield* session.remove(created.id) }), ) - it.instance("does not replay an assistant tail when compaction starts inside a turn", () => + it.instance("retains an assistant tail when compaction starts inside a turn", () => withSession(({ session, sessionID }) => Effect.gen(function* () { const u1 = yield* addUser(sessionID, "first") @@ -819,7 +819,7 @@ describe("MessageV2.filterCompacted", () => { const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - expect(result.map((item) => item.info.id)).toEqual([c1, s1, u3, a4]) + expect(result.map((item) => item.info.id)).toEqual([c1, s1, a3, u3, a4]) }), ), ) @@ -891,7 +891,7 @@ describe("MessageV2.filterCompacted", () => { const result = MessageV2.filterCompacted(MessageV2.stream(sessionID)) - expect(result.map((item) => item.info.id)).toEqual([c2, s2, u4, a4]) + expect(result.map((item) => item.info.id)).toEqual([c2, s2, u3, a3, u4, a4]) }), ), ) From 4cf088ae84813bd724352c37803708de854eaf11 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:34:12 -0400 Subject: [PATCH 143/378] test: migrate instance bootstrap to Effect runner (#27144) --- .../test/project/instance-bootstrap.test.ts | 111 ++++++++++-------- 1 file changed, 61 insertions(+), 50 deletions(-) diff --git a/packages/opencode/test/project/instance-bootstrap.test.ts b/packages/opencode/test/project/instance-bootstrap.test.ts index 71521a765..4be2a7611 100644 --- a/packages/opencode/test/project/instance-bootstrap.test.ts +++ b/packages/opencode/test/project/instance-bootstrap.test.ts @@ -1,11 +1,16 @@ -import { afterEach, expect, test } from "bun:test" +import { afterEach, expect } from "bun:test" import { existsSync } from "node:fs" import path from "node:path" import { pathToFileURL } from "node:url" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { Effect, Layer } from "effect" import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap" -import { WithInstance } from "../../src/project/with-instance" -import { InstanceRuntime } from "../../src/project/instance-runtime" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" +import { InstanceLayer } from "../../src/project/instance-layer" +import { InstanceStore } from "../../src/project/instance-store" +import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" + +const it = testEffect(Layer.mergeAll(InstanceLayer.layer, CrossSpawnSpawner.defaultLayer)) // InstanceBootstrap must run before any code touches the instance — // originally tracked by PRs #25389 and #25449, now a permanent @@ -19,58 +24,64 @@ afterEach(async () => { await disposeAllInstances() }) -async function bootstrapFixture() { - return tmpdir({ - init: async (dir) => { - const marker = path.join(dir, "config-hook-fired") - const pluginFile = path.join(dir, "plugin.ts") - await Bun.write( - pluginFile, - [ - `const MARKER = ${JSON.stringify(marker)}`, - "export default async () => ({", - " config: async () => {", - ' await Bun.write(MARKER, "ran")', - " },", - "})", - "", - ].join("\n"), - ) - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - plugin: [pathToFileURL(pluginFile).href], - }), - ) - return marker - }, - }) -} +const bootstrapFixture = Effect.gen(function* () { + const dir = yield* tmpdirScoped({ git: true }) + const marker = path.join(dir, "config-hook-fired") + const pluginFile = path.join(dir, "plugin.ts") + yield* Effect.promise(() => + Bun.write( + pluginFile, + [ + `const MARKER = ${JSON.stringify(marker)}`, + "export default async () => ({", + " config: async () => {", + ' await Bun.write(MARKER, "ran")', + " },", + "})", + "", + ].join("\n"), + ), + ) + yield* Effect.promise(() => + Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + plugin: [pathToFileURL(pluginFile).href], + }), + ), + ) + return { directory: dir, marker } +}) -test("WithInstance.provide runs InstanceBootstrap before fn", async () => { - await using tmp = await bootstrapFixture() +it.live("InstanceStore.provide runs InstanceBootstrap before effect", () => + Effect.gen(function* () { + const tmp = yield* bootstrapFixture + const store = yield* InstanceStore.Service - await WithInstance.provide({ - directory: tmp.path, - fn: async () => "ok", - }) + yield* store.provide({ directory: tmp.directory }, Effect.succeed("ok")) - expect(existsSync(tmp.extra)).toBe(true) -}) + expect(existsSync(tmp.marker)).toBe(true) + }), +) -test("CLI bootstrap runs InstanceBootstrap before callback", async () => { - await using tmp = await bootstrapFixture() +it.live("CLI bootstrap runs InstanceBootstrap before callback", () => + Effect.gen(function* () { + const tmp = yield* bootstrapFixture - await cliBootstrap(tmp.path, async () => "ok") + yield* Effect.promise(() => cliBootstrap(tmp.directory, async () => "ok")) - expect(existsSync(tmp.extra)).toBe(true) -}) + expect(existsSync(tmp.marker)).toBe(true) + }), +) -test("InstanceRuntime.reloadInstance runs InstanceBootstrap", async () => { - await using tmp = await bootstrapFixture() +it.live("InstanceStore.reload runs InstanceBootstrap", () => + Effect.gen(function* () { + const tmp = yield* bootstrapFixture + const store = yield* InstanceStore.Service - await InstanceRuntime.reloadInstance({ directory: tmp.path }) + yield* store.reload({ directory: tmp.directory }) - expect(existsSync(tmp.extra)).toBe(true) -}) + expect(existsSync(tmp.marker)).toBe(true) + }), +) From 3c34f6704b15ae491750fe6b78d244cfc24d4a7a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:41:34 -0400 Subject: [PATCH 144/378] test: migrate auth override plugin test (#27140) --- .../test/plugin/auth-override.test.ts | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/packages/opencode/test/plugin/auth-override.test.ts b/packages/opencode/test/plugin/auth-override.test.ts index c77c0ca1c..402d755da 100644 --- a/packages/opencode/test/plugin/auth-override.test.ts +++ b/packages/opencode/test/plugin/auth-override.test.ts @@ -1,15 +1,19 @@ import { describe, expect, test } from "bun:test" import path from "path" -import fs from "fs/promises" import { pathToFileURL } from "url" import { Effect, Layer } from "effect" -import { provideTestInstance, tmpdir } from "../fixture/fixture" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" import { ProviderAuth } from "@/provider/auth" import { ProviderID } from "../../src/provider/schema" import { Plugin } from "@/plugin" import { Auth } from "@/auth" import { Bus } from "@/bus" import { TestConfig } from "../fixture/config" +import { testEffect } from "../lib/effect" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" + +const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, AppFileSystem.defaultLayer)) function layer(directory: string, plugins: string[]) { return ProviderAuth.layer.pipe( @@ -37,13 +41,15 @@ function layer(directory: string, plugins: string[]) { } describe("plugin.auth-override", () => { - test("user plugin overrides built-in github-copilot auth", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - const pluginDir = path.join(dir, ".opencode", "plugin") - await fs.mkdir(pluginDir, { recursive: true }) + it.instance( + "user plugin overrides built-in github-copilot auth", + () => + Effect.gen(function* () { + const tmp = yield* TestInstance + const fs = yield* AppFileSystem.Service + const pluginDir = path.join(tmp.directory, ".opencode", "plugin") - await Bun.write( + yield* fs.writeWithDirs( path.join(pluginDir, "custom-copilot-auth.ts"), [ "export default {", @@ -61,37 +67,26 @@ describe("plugin.auth-override", () => { "", ].join("\n"), ) - }, - }) - await using plain = await tmpdir() + const plain = yield* tmpdirScoped({ git: true }) + const plugin = pathToFileURL(path.join(pluginDir, "custom-copilot-auth.ts")).href + const methods = yield* ProviderAuth.Service.use((svc) => svc.methods()).pipe( + Effect.provide(layer(tmp.directory, [plugin])), + ) + const plainMethods = yield* ProviderAuth.Service.use((svc) => svc.methods()).pipe( + Effect.provide(layer(plain, [])), + provideInstance(plain), + ) - const plugin = pathToFileURL(path.join(tmp.path, ".opencode", "plugin", "custom-copilot-auth.ts")).href - const [methods, plainMethods] = await Promise.all([ - provideTestInstance({ - directory: tmp.path, - fn: async () => { - return Effect.runPromise( - ProviderAuth.Service.use((svc) => svc.methods()).pipe(Effect.provide(layer(tmp.path, [plugin]))), - ) - }, + const copilot = methods[ProviderID.make("github-copilot")] + expect(copilot).toBeDefined() + expect(copilot.length).toBe(1) + expect(copilot[0].label).toBe("Test Override Auth") + expect(plainMethods[ProviderID.make("github-copilot")][0].label).not.toBe("Test Override Auth") }), - provideTestInstance({ - directory: plain.path, - fn: async () => { - return Effect.runPromise( - ProviderAuth.Service.use((svc) => svc.methods()).pipe(Effect.provide(layer(plain.path, []))), - ) - }, - }), - ]) - - const copilot = methods[ProviderID.make("github-copilot")] - expect(copilot).toBeDefined() - expect(copilot.length).toBe(1) - expect(copilot[0].label).toBe("Test Override Auth") - expect(plainMethods[ProviderID.make("github-copilot")][0].label).not.toBe("Test Override Auth") - }, 30000) + { git: true }, + 30000, + ) }) const file = path.join(import.meta.dir, "../../src/plugin/index.ts") From 0fb55b4f1a0330d531427a517824ca3714ae8307 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:45:39 -0400 Subject: [PATCH 145/378] test(project): migrate global project tests to Effect runner (#27142) --- .../test/project/migrate-global.test.ts | 204 +++++++++--------- 1 file changed, 105 insertions(+), 99 deletions(-) diff --git a/packages/opencode/test/project/migrate-global.test.ts b/packages/opencode/test/project/migrate-global.test.ts index c476c108b..6efd670c5 100644 --- a/packages/opencode/test/project/migrate-global.test.ts +++ b/packages/opencode/test/project/migrate-global.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect } from "bun:test" import { Project } from "@/project/project" import { Database } from "@/storage/db" import { eq } from "drizzle-orm" @@ -8,19 +8,14 @@ import { ProjectID } from "../../src/project/schema" import { SessionID } from "../../src/session/schema" import * as Log from "@opencode-ai/core/util/log" import { $ } from "bun" -import { tmpdir } from "../fixture/fixture" -import { Effect } from "effect" +import { tmpdirScoped } from "../fixture/fixture" +import { Effect, Layer } from "effect" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { testEffect } from "../lib/effect" -Log.init({ print: false }) +void Log.init({ print: false }) -function run(fn: (svc: Project.Interface) => Effect.Effect) { - return Effect.runPromise( - Effect.gen(function* () { - const svc = yield* Project.Service - return yield* fn(svc) - }).pipe(Effect.provide(Project.defaultLayer)), - ) -} +const it = testEffect(Layer.mergeAll(Project.defaultLayer, CrossSpawnSpawner.defaultLayer)) function legacySessionID() { // Global-session migration covers persisted IDs from before prefixed session IDs. @@ -63,91 +58,102 @@ function ensureGlobal() { } describe("migrateFromGlobal", () => { - test("migrates global sessions on first project creation", async () => { - // 1. Start with git init but no commits — creates "global" project row - await using tmp = await tmpdir() - await $`git init`.cwd(tmp.path).quiet() - await $`git config user.name "Test"`.cwd(tmp.path).quiet() - await $`git config user.email "test@opencode.test"`.cwd(tmp.path).quiet() - await $`git config commit.gpgsign false`.cwd(tmp.path).quiet() - const { project: pre } = await run((svc) => svc.fromDirectory(tmp.path)) - expect(pre.id).toBe(ProjectID.global) - - // 2. Seed a session under "global" with matching directory - const id = legacySessionID() - seed({ id, dir: tmp.path, project: ProjectID.global }) - - // 3. Make a commit so the project gets a real ID - await $`git commit --allow-empty -m "root"`.cwd(tmp.path).quiet() - - const { project: real } = await run((svc) => svc.fromDirectory(tmp.path)) - expect(real.id).not.toBe(ProjectID.global) - - // 4. The session should have been migrated to the real project ID - const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) - expect(row).toBeDefined() - expect(row!.project_id).toBe(real.id) - }) - - test("migrates global sessions even when project row already exists", async () => { - // 1. Create a repo with a commit — real project ID created immediately - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) - expect(project.id).not.toBe(ProjectID.global) - - // 2. Ensure "global" project row exists (as it would from a prior no-git session) - ensureGlobal() - - // 3. Seed a session under "global" with matching directory. - // This simulates a session created before git init that wasn't - // present when the real project row was first created. - const id = legacySessionID() - seed({ id, dir: tmp.path, project: ProjectID.global }) - - // 4. Call fromDirectory again — project row already exists, - // so the current code skips migration entirely. This is the bug. - await run((svc) => svc.fromDirectory(tmp.path)) - - const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) - expect(row).toBeDefined() - expect(row!.project_id).toBe(project.id) - }) - - test("does not claim sessions with empty directory", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) - expect(project.id).not.toBe(ProjectID.global) - - ensureGlobal() - - // Legacy sessions may lack a directory value. - // Without a matching origin directory, they should remain global. - const id = legacySessionID() - seed({ id, dir: "", project: ProjectID.global }) - - await run((svc) => svc.fromDirectory(tmp.path)) - - const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) - expect(row).toBeDefined() - expect(row!.project_id).toBe(ProjectID.global) - }) - - test("does not steal sessions from unrelated directories", async () => { - await using tmp = await tmpdir({ git: true }) - const { project } = await run((svc) => svc.fromDirectory(tmp.path)) - expect(project.id).not.toBe(ProjectID.global) - - ensureGlobal() - - // Seed a session under "global" but for a DIFFERENT directory - const id = legacySessionID() - seed({ id, dir: "/some/other/dir", project: ProjectID.global }) - - await run((svc) => svc.fromDirectory(tmp.path)) - - const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) - expect(row).toBeDefined() - // Should remain under "global" — not stolen - expect(row!.project_id).toBe(ProjectID.global) - }) + it.live("migrates global sessions on first project creation", () => + Effect.gen(function* () { + // 1. Start with git init but no commits — creates "global" project row + const tmp = yield* tmpdirScoped() + yield* Effect.promise(() => $`git init`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git config user.name "Test"`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git config user.email "test@opencode.test"`.cwd(tmp).quiet()) + yield* Effect.promise(() => $`git config commit.gpgsign false`.cwd(tmp).quiet()) + const projects = yield* Project.Service + const { project: pre } = yield* projects.fromDirectory(tmp) + expect(pre.id).toBe(ProjectID.global) + + // 2. Seed a session under "global" with matching directory + const id = legacySessionID() + yield* Effect.sync(() => seed({ id, dir: tmp, project: ProjectID.global })) + + // 3. Make a commit so the project gets a real ID + yield* Effect.promise(() => $`git commit --allow-empty -m "root"`.cwd(tmp).quiet()) + + const { project: real } = yield* projects.fromDirectory(tmp) + expect(real.id).not.toBe(ProjectID.global) + + // 4. The session should have been migrated to the real project ID + const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) + expect(row).toBeDefined() + expect(row!.project_id).toBe(real.id) + }), + ) + + it.live("migrates global sessions even when project row already exists", () => + Effect.gen(function* () { + // 1. Create a repo with a commit — real project ID created immediately + const tmp = yield* tmpdirScoped({ git: true }) + const projects = yield* Project.Service + const { project } = yield* projects.fromDirectory(tmp) + expect(project.id).not.toBe(ProjectID.global) + + // 2. Ensure "global" project row exists (as it would from a prior no-git session) + yield* Effect.sync(() => ensureGlobal()) + + // 3. Seed a session under "global" with matching directory. + // This simulates a session created before git init that wasn't + // present when the real project row was first created. + const id = legacySessionID() + yield* Effect.sync(() => seed({ id, dir: tmp, project: ProjectID.global })) + + // 4. Call fromDirectory again — project row already exists, + // so the current code skips migration entirely. This is the bug. + yield* projects.fromDirectory(tmp) + + const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) + expect(row).toBeDefined() + expect(row!.project_id).toBe(project.id) + }), + ) + + it.live("does not claim sessions with empty directory", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped({ git: true }) + const projects = yield* Project.Service + const { project } = yield* projects.fromDirectory(tmp) + expect(project.id).not.toBe(ProjectID.global) + + yield* Effect.sync(() => ensureGlobal()) + + // Legacy sessions may lack a directory value. + // Without a matching origin directory, they should remain global. + const id = legacySessionID() + yield* Effect.sync(() => seed({ id, dir: "", project: ProjectID.global })) + + yield* projects.fromDirectory(tmp) + + const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) + expect(row).toBeDefined() + expect(row!.project_id).toBe(ProjectID.global) + }), + ) + + it.live("does not steal sessions from unrelated directories", () => + Effect.gen(function* () { + const tmp = yield* tmpdirScoped({ git: true }) + const projects = yield* Project.Service + const { project } = yield* projects.fromDirectory(tmp) + expect(project.id).not.toBe(ProjectID.global) + + yield* Effect.sync(() => ensureGlobal()) + + // Seed a session under "global" but for a DIFFERENT directory + const id = legacySessionID() + yield* Effect.sync(() => seed({ id, dir: "/some/other/dir", project: ProjectID.global })) + + yield* projects.fromDirectory(tmp) + const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) + expect(row).toBeDefined() + // Should remain under "global" — not stolen + expect(row!.project_id).toBe(ProjectID.global) + }), + ) }) From d9f9f1553b439bd3c811322484387479b6b26a2d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 16:48:07 -0400 Subject: [PATCH 146/378] test: use Effect file services in migrated tests (#27154) --- .../opencode/test/skill/discovery.test.ts | 38 +++++++++++-------- .../opencode/test/snapshot/snapshot.test.ts | 24 +++++------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/opencode/test/skill/discovery.test.ts b/packages/opencode/test/skill/discovery.test.ts index 0b07d4df0..074992c56 100644 --- a/packages/opencode/test/skill/discovery.test.ts +++ b/packages/opencode/test/skill/discovery.test.ts @@ -1,5 +1,6 @@ import { describe, expect, beforeAll, afterAll } from "bun:test" -import { Effect } from "effect" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { Effect, Layer } from "effect" import { Discovery } from "../../src/skill/discovery" import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util/filesystem" @@ -13,7 +14,7 @@ let downloadCount = 0 const fixturePath = path.join(import.meta.dir, "../fixture/skills") const cacheDir = path.join(Global.Path.cache, "skills") -const it = testEffect(Discovery.defaultLayer) +const it = testEffect(Layer.mergeAll(Discovery.defaultLayer, AppFileSystem.defaultLayer)) beforeAll(async () => { await rm(cacheDir, { recursive: true, force: true }) @@ -49,36 +50,37 @@ afterAll(async () => { }) describe("Discovery.pull", () => { - const pull = Effect.fn("DiscoveryTest.pull")(function* (url: string) { - return yield* Discovery.Service.use((s) => s.pull(url)) - }) - it.live("downloads skills from cloudflare url", () => Effect.gen(function* () { - const dirs = yield* pull(CLOUDFLARE_SKILLS_URL) + const fsys = yield* AppFileSystem.Service + const discovery = yield* Discovery.Service + const dirs = yield* discovery.pull(CLOUDFLARE_SKILLS_URL) expect(dirs.length).toBeGreaterThan(0) for (const dir of dirs) { expect(dir).toStartWith(cacheDir) const md = path.join(dir, "SKILL.md") - expect(yield* Effect.promise(() => Filesystem.exists(md))).toBe(true) + expect(yield* fsys.existsSafe(md)).toBe(true) } }), ) it.live("url without trailing slash works", () => Effect.gen(function* () { - const dirs = yield* pull(CLOUDFLARE_SKILLS_URL.replace(/\/$/, "")) + const fsys = yield* AppFileSystem.Service + const discovery = yield* Discovery.Service + const dirs = yield* discovery.pull(CLOUDFLARE_SKILLS_URL.replace(/\/$/, "")) expect(dirs.length).toBeGreaterThan(0) for (const dir of dirs) { const md = path.join(dir, "SKILL.md") - expect(yield* Effect.promise(() => Filesystem.exists(md))).toBe(true) + expect(yield* fsys.existsSafe(md)).toBe(true) } }), ) it.live("returns empty array for invalid url", () => Effect.gen(function* () { - const dirs = yield* pull(`http://localhost:${server.port}/invalid-url/`) + const discovery = yield* Discovery.Service + const dirs = yield* discovery.pull(`http://localhost:${server.port}/invalid-url/`) expect(dirs).toEqual([]) }), ) @@ -86,20 +88,23 @@ describe("Discovery.pull", () => { it.live("returns empty array for non-json response", () => Effect.gen(function* () { // any url not explicitly handled in server returns 404 text "Not Found" - const dirs = yield* pull(`http://localhost:${server.port}/some-other-path/`) + const discovery = yield* Discovery.Service + const dirs = yield* discovery.pull(`http://localhost:${server.port}/some-other-path/`) expect(dirs).toEqual([]) }), ) it.live("downloads reference files alongside SKILL.md", () => Effect.gen(function* () { - const dirs = yield* pull(CLOUDFLARE_SKILLS_URL) + const fsys = yield* AppFileSystem.Service + const discovery = yield* Discovery.Service + const dirs = yield* discovery.pull(CLOUDFLARE_SKILLS_URL) // find a skill dir that should have reference files (e.g. agents-sdk) const agentsSdk = dirs.find((d) => d.endsWith(path.sep + "agents-sdk")) expect(agentsSdk).toBeDefined() if (agentsSdk) { const refs = path.join(agentsSdk, "references") - expect(yield* Effect.promise(() => Filesystem.exists(path.join(agentsSdk, "SKILL.md")))).toBe(true) + expect(yield* fsys.existsSafe(path.join(agentsSdk, "SKILL.md"))).toBe(true) // agents-sdk has reference files per the index const refDir = yield* Effect.promise(() => Array.fromAsync(new Bun.Glob("**/*.md").scan({ cwd: refs, onlyFiles: true })), @@ -114,15 +119,16 @@ describe("Discovery.pull", () => { // clear dir and downloadCount yield* Effect.promise(() => rm(cacheDir, { recursive: true, force: true })) downloadCount = 0 + const discovery = yield* Discovery.Service // first pull to populate cache - const first = yield* pull(CLOUDFLARE_SKILLS_URL) + const first = yield* discovery.pull(CLOUDFLARE_SKILLS_URL) expect(first.length).toBeGreaterThan(0) const firstCount = downloadCount expect(firstCount).toBeGreaterThan(0) // second pull should return same results from cache - const second = yield* pull(CLOUDFLARE_SKILLS_URL) + const second = yield* discovery.pull(CLOUDFLARE_SKILLS_URL) expect(second.length).toBe(first.length) expect(second.sort()).toEqual(first.sort()) diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts index fa167281b..de60d58b2 100644 --- a/packages/opencode/test/snapshot/snapshot.test.ts +++ b/packages/opencode/test/snapshot/snapshot.test.ts @@ -1,15 +1,15 @@ import { afterEach, expect } from "bun:test" import { $ } from "bun" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import fs from "fs/promises" import path from "path" -import { Effect, Fiber } from "effect" +import { Effect, Fiber, Layer } from "effect" import { Snapshot } from "../../src/snapshot" -import { Filesystem } from "@/util/filesystem" import { disposeAllInstances, provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Snapshot.defaultLayer) +const it = testEffect(Layer.mergeAll(Snapshot.defaultLayer, AppFileSystem.defaultLayer)) // Git always outputs /-separated paths internally. Snapshot.patch() joins them // with path.join (which produces \ on Windows) then normalizes back to /. @@ -27,17 +27,13 @@ const exec = (cwd: string, command: string[]) => if (code !== 0) throw new Error(`${command.join(" ")} failed: ${await new Response(proc.stderr).text()}`) }) -const write = (file: string, content: string | Uint8Array) => Effect.promise(() => Filesystem.write(file, content)) -const readText = (file: string) => Effect.promise(() => fs.readFile(file, "utf-8")) -const exists = (file: string) => - Effect.promise(() => - fs - .access(file) - .then(() => true) - .catch(() => false), - ) -const mkdirp = (dir: string) => Effect.promise(() => fs.mkdir(dir, { recursive: true })) -const rm = (file: string) => Effect.promise(() => fs.rm(file, { recursive: true, force: true })) +const write = (file: string, content: string | Uint8Array) => + AppFileSystem.Service.use((fs) => fs.writeWithDirs(file, content)) +const readText = (file: string) => AppFileSystem.Service.use((fs) => fs.readFileString(file)) +const exists = (file: string) => AppFileSystem.Service.use((fs) => fs.existsSafe(file)) +const mkdirp = (dir: string) => AppFileSystem.Service.use((fs) => fs.ensureDir(dir)) +const rm = (file: string) => + AppFileSystem.Service.use((fs) => fs.remove(file, { recursive: true, force: true }).pipe(Effect.ignore)) const initialize = Effect.fn("SnapshotTest.initialize")(function* (dir: string) { const unique = Math.random().toString(36).slice(2) From 159964b1724b4424a914ea49000314ae978bf2d3 Mon Sep 17 00:00:00 2001 From: Musa Date: Tue, 12 May 2026 14:22:40 -0700 Subject: [PATCH 147/378] feat(plugin): add DigitalOcean OAuth + Inference Routers (#26095) --- packages/opencode/src/cli/cmd/providers.ts | 5 +- packages/opencode/src/plugin/digitalocean.ts | 407 ++++++++++++++++++ packages/opencode/src/plugin/index.ts | 2 + packages/opencode/src/provider/auth.ts | 1 + .../test/provider/digitalocean.test.ts | 144 +++++++ .../test/tool/fixtures/models-api.json | 24 ++ packages/plugin/src/index.ts | 8 +- packages/sdk/js/src/gen/types.gen.ts | 3 + .../assets/icons/provider/digitalocean.svg | 6 + .../src/components/provider-icons/sprite.svg | 14 + .../ui/src/components/provider-icons/types.ts | 1 + packages/web/src/content/docs/providers.mdx | 82 ++++ 12 files changed, 692 insertions(+), 5 deletions(-) create mode 100644 packages/opencode/src/plugin/digitalocean.ts create mode 100644 packages/opencode/test/provider/digitalocean.test.ts create mode 100644 packages/ui/src/assets/icons/provider/digitalocean.svg diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index 749139e2d..426ea89fc 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -124,6 +124,7 @@ const handlePluginAuth = Effect.fn("Cli.providers.pluginAuth")(function* ( yield* put(saveProvider, { type: "api", key: result.key, + ...(result.metadata ? { metadata: result.metadata } : {}), }) } yield* spinner.stop("Login successful") @@ -156,6 +157,7 @@ const handlePluginAuth = Effect.fn("Cli.providers.pluginAuth")(function* ( yield* put(saveProvider, { type: "api", key: result.key, + ...(result.metadata ? { metadata: result.metadata } : {}), }) } yield* Prompt.log.success("Login successful") @@ -191,10 +193,11 @@ const handlePluginAuth = Effect.fn("Cli.providers.pluginAuth")(function* ( } if (result.type === "success") { const saveProvider = result.provider ?? provider + const merged = { ...(metadata.metadata ?? {}), ...(result.metadata ?? {}) } yield* put(saveProvider, { type: "api", key: result.key ?? apiKey, - ...metadata, + ...(Object.keys(merged).length ? { metadata: merged } : {}), }) yield* Prompt.log.success("Login successful") } diff --git a/packages/opencode/src/plugin/digitalocean.ts b/packages/opencode/src/plugin/digitalocean.ts new file mode 100644 index 000000000..31656656f --- /dev/null +++ b/packages/opencode/src/plugin/digitalocean.ts @@ -0,0 +1,407 @@ +import type { Hooks, PluginInput } from "@opencode-ai/plugin" +import type { Model } from "@opencode-ai/sdk/v2" +import * as Log from "@opencode-ai/core/util/log" +import { InstallationVersion } from "@opencode-ai/core/installation/version" +import { createServer } from "http" + +const log = Log.create({ service: "plugin.digitalocean" }) + +const DO_OAUTH_CLIENT_ID = "b1a6c5158156caac821fd1b30253ca8acb52454a48fa744420e41889cb589f82" +const DO_AUTHORIZE_URL = "https://cloud.digitalocean.com/v1/oauth/authorize" +const DO_API_BASE = "https://api.digitalocean.com" +const DO_INFERENCE_BASE = "https://inference.do-ai.run/v1" +const OAUTH_PORT = 1456 +const OAUTH_REDIRECT_PATH = "/auth/callback" +const OAUTH_TOKEN_PATH = "/auth/token" +const ROUTER_REFRESH_INTERVAL_MS = 5 * 60 * 1000 +const MAK_NAME_PREFIX = "opencode-oauth" + +interface ImplicitTokenPayload { + access_token: string + expires_in: number + state: string +} + +interface PendingOAuth { + state: string + resolve: (tokens: ImplicitTokenPayload) => void + reject: (error: Error) => void +} + +interface ApiKeyInfo { + uuid: string + name: string + secret_key: string +} + +interface RouterEntry { + name: string + uuid?: string + description?: string +} + +let oauthServer: ReturnType | undefined +let pendingOAuth: PendingOAuth | undefined + +function generateState(): string { + const bytes = crypto.getRandomValues(new Uint8Array(32)) + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") +} + +function redirectUri(): string { + return `http://localhost:${OAUTH_PORT}${OAUTH_REDIRECT_PATH}` +} + +function buildAuthorizeUrl(state: string): string { + const params = new URLSearchParams({ + response_type: "token", + client_id: DO_OAUTH_CLIENT_ID, + redirect_uri: redirectUri(), + scope: "read write", + state, + }) + return `${DO_AUTHORIZE_URL}?${params.toString()}` +} + +const HTML_CALLBACK = ` + + + + OpenCode - DigitalOcean Authorization + + + +
+

Finishing sign-in...

+

You can close this window once it says you're signed in.

+
+ + +` + +async function startOAuthServer(): Promise { + if (oauthServer) return + oauthServer = createServer((req, res) => { + const url = new URL(req.url || "/", `http://localhost:${OAUTH_PORT}`) + + if (req.method === "GET" && url.pathname === OAUTH_REDIRECT_PATH) { + res.writeHead(200, { "Content-Type": "text/html" }) + res.end(HTML_CALLBACK) + return + } + + if (req.method === "POST" && url.pathname === OAUTH_TOKEN_PATH) { + const chunks: Buffer[] = [] + req.on("data", (chunk: Buffer) => chunks.push(chunk)) + req.on("end", () => { + const raw = Buffer.concat(chunks).toString("utf8") + let body: Record = {} + try { + body = raw ? JSON.parse(raw) : {} + } catch { + body = {} + } + if (!pendingOAuth) { + res.writeHead(409, { "Content-Type": "application/json" }) + res.end(JSON.stringify({ error: "no_pending_oauth" })) + return + } + if (body.error) { + const message = body.error_description || body.error || "OAuth error" + pendingOAuth.reject(new Error(String(message))) + pendingOAuth = undefined + res.writeHead(200, { "Content-Type": "application/json" }) + res.end(JSON.stringify({ ok: true })) + return + } + if (!body.access_token) { + pendingOAuth.reject(new Error("Missing access_token in callback")) + pendingOAuth = undefined + res.writeHead(400, { "Content-Type": "application/json" }) + res.end(JSON.stringify({ error: "missing_access_token" })) + return + } + if (body.state !== pendingOAuth.state) { + pendingOAuth.reject(new Error("Invalid state - potential CSRF attack")) + pendingOAuth = undefined + res.writeHead(400, { "Content-Type": "application/json" }) + res.end(JSON.stringify({ error: "invalid_state" })) + return + } + const expires = parseInt(body.expires_in || "0", 10) + pendingOAuth.resolve({ + access_token: body.access_token, + expires_in: Number.isFinite(expires) && expires > 0 ? expires : 60 * 60 * 24 * 30, + state: body.state, + }) + pendingOAuth = undefined + res.writeHead(200, { "Content-Type": "application/json" }) + res.end(JSON.stringify({ ok: true })) + }) + return + } + + res.writeHead(404) + res.end("Not found") + }) + + await new Promise((resolve, reject) => { + oauthServer!.listen(OAUTH_PORT, () => { + log.info("digitalocean oauth server started", { port: OAUTH_PORT }) + resolve() + }) + oauthServer!.on("error", reject) + }) +} + +function stopOAuthServer() { + if (!oauthServer) return + oauthServer.close(() => log.info("digitalocean oauth server stopped")) + oauthServer = undefined +} + +function waitForOAuthCallback(state: string): Promise { + return new Promise((resolve, reject) => { + const timeout = setTimeout( + () => { + if (pendingOAuth) { + pendingOAuth = undefined + reject(new Error("OAuth callback timeout - authorization took too long")) + } + }, + 5 * 60 * 1000, + ) + pendingOAuth = { + state, + resolve: (tokens) => { + clearTimeout(timeout) + resolve(tokens) + }, + reject: (error) => { + clearTimeout(timeout) + reject(error) + }, + } + }) +} + +async function createModelAccessKey(bearer: string): Promise { + // Suffix-on-collision strategy keeps re-`/connect` non-destructive. + const name = `${MAK_NAME_PREFIX}-${Math.floor(Date.now() / 1000)}` + const res = await fetch(`${DO_API_BASE}/v2/gen-ai/models/api_keys`, { + method: "POST", + headers: { + Authorization: `Bearer ${bearer}`, + "Content-Type": "application/json", + "User-Agent": `opencode/${InstallationVersion}`, + }, + body: JSON.stringify({ name }), + }) + if (!res.ok) { + const body = await res.text().catch(() => "") + throw new Error(`Failed to create Model Access Key (${res.status}): ${body}`) + } + const data = (await res.json()) as { api_key_info?: ApiKeyInfo } + if (!data.api_key_info?.secret_key) throw new Error("Model Access Key response missing secret_key") + return data.api_key_info +} + +async function listRouters(bearer: string): Promise<{ ok: true; routers: RouterEntry[] } | { ok: false; status: number }> { + const res = await fetch(`${DO_API_BASE}/v2/gen-ai/models/routers`, { + headers: { + Authorization: `Bearer ${bearer}`, + Accept: "application/json", + "User-Agent": `opencode/${InstallationVersion}`, + }, + signal: AbortSignal.timeout(10_000), + }).catch(() => undefined) + if (!res) return { ok: false, status: 0 } + if (!res.ok) return { ok: false, status: res.status } + const body = (await res.json().catch(() => undefined)) as { model_routers?: RouterEntry[] } | undefined + return { ok: true, routers: body?.model_routers ?? [] } +} + +function routerModel(router: RouterEntry, providerID: string): Model { + const id = `router:${router.name}` + return { + id, + providerID, + name: router.name, + family: "digitalocean-inference-routers", + api: { id, url: DO_INFERENCE_BASE, npm: "@ai-sdk/openai-compatible" }, + status: "active", + headers: {}, + options: {}, + cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, + limit: { context: 128_000, output: 8_192 }, + capabilities: { + temperature: true, + reasoning: false, + attachment: false, + toolcall: true, + input: { text: true, audio: false, image: false, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + release_date: "", + variants: {}, + } +} + +function parseRoutersJSON(raw: string | undefined): RouterEntry[] { + if (!raw) return [] + try { + const parsed = JSON.parse(raw) + if (!Array.isArray(parsed)) return [] + return parsed.flatMap((r) => (r && typeof r.name === "string" ? [{ name: r.name, uuid: r.uuid, description: r.description }] : [])) + } catch { + return [] + } +} + +export async function DigitalOceanAuthPlugin(input: PluginInput): Promise { + return { + provider: { + id: "digitalocean", + async models(provider, ctx) { + const baseModels = provider.models + if (ctx.auth?.type !== "api") return baseModels + + const metadata = ctx.auth.metadata ?? {} + const oauthAccess = metadata["oauth_access"] + const oauthExpires = parseInt(metadata["oauth_expires"] || "0", 10) + const fetchedAt = parseInt(metadata["routers_fetched_at"] || "0", 10) + const cached = parseRoutersJSON(metadata["routers"]) + + let routers = cached + const stale = Date.now() - fetchedAt > ROUTER_REFRESH_INTERVAL_MS + const bearerValid = oauthAccess && oauthExpires > Date.now() + + if (bearerValid && stale) { + const result = await listRouters(oauthAccess) + if (result.ok) { + routers = result.routers + const updated: Record = { + ...metadata, + routers: JSON.stringify(routers.map((r) => ({ name: r.name, uuid: r.uuid, description: r.description }))), + routers_fetched_at: String(Date.now()), + } + await input.client.auth + .set({ + path: { id: "digitalocean" }, + body: { type: "api", key: ctx.auth.key, metadata: updated }, + }) + .catch((err) => log.warn("failed to persist refreshed routers", { error: err })) + } else if (result.status === 401 || result.status === 403) { + log.warn("digitalocean oauth bearer rejected; using cached routers", { status: result.status }) + } else if (result.status !== 0) { + log.warn("digitalocean router refresh failed", { status: result.status }) + } + } + + const merged: Record = { ...baseModels } + for (const router of routers) { + const id = `router:${router.name}` + if (merged[id]) continue + merged[id] = routerModel(router, "digitalocean") + } + return merged + }, + }, + auth: { + provider: "digitalocean", + methods: [ + { + type: "oauth", + label: "Login with DigitalOcean", + async authorize() { + await startOAuthServer() + const state = generateState() + const callbackPromise = waitForOAuthCallback(state) + return { + url: buildAuthorizeUrl(state), + instructions: + "Sign in to DigitalOcean in your browser. OpenCode will create a Model Access Key named opencode-oauth-* and load your Inference Routers. Re-run /connect to refresh routers later.", + method: "auto" as const, + async callback() { + try { + const tokens = await callbackPromise + const apiKeyInfo = await createModelAccessKey(tokens.access_token) + const routerResult = await listRouters(tokens.access_token) + const routers = routerResult.ok ? routerResult.routers : [] + if (!routerResult.ok) { + log.warn("digitalocean initial router fetch failed", { status: routerResult.status }) + } + return { + type: "success" as const, + provider: "digitalocean", + key: apiKeyInfo.secret_key, + metadata: { + mak_uuid: apiKeyInfo.uuid, + mak_name: apiKeyInfo.name, + oauth_access: tokens.access_token, + oauth_expires: String(Date.now() + tokens.expires_in * 1000), + routers: JSON.stringify( + routers.map((r) => ({ name: r.name, uuid: r.uuid, description: r.description })), + ), + routers_fetched_at: String(Date.now()), + }, + } + } catch (err) { + log.error("digitalocean oauth callback failed", { error: err }) + return { type: "failed" as const } + } finally { + stopOAuthServer() + } + }, + } + }, + }, + { + type: "api", + label: "Paste Model Access Key", + }, + ], + }, + } +} diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 7a7f260df..68d47916c 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -19,6 +19,7 @@ import { gitlabAuthPlugin as GitlabAuthPlugin } from "opencode-gitlab-auth" import { PoeAuthPlugin } from "opencode-poe-auth" import { CloudflareAIGatewayAuthPlugin, CloudflareWorkersAuthPlugin } from "./cloudflare" import { AzureAuthPlugin } from "./azure" +import { DigitalOceanAuthPlugin } from "./digitalocean" import { Effect, Layer, Context, Stream } from "effect" import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" @@ -64,6 +65,7 @@ const INTERNAL_PLUGINS: PluginInstance[] = [ CloudflareWorkersAuthPlugin, CloudflareAIGatewayAuthPlugin, AzureAuthPlugin, + DigitalOceanAuthPlugin, ] function isServerPlugin(value: unknown): value is PluginInstance { diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index ba2a8c744..b63e1eaf4 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -197,6 +197,7 @@ export const layer: Layer.Layer = yield* auth.set(input.providerID, { type: "api", key: result.key, + ...(result.metadata ? { metadata: result.metadata } : {}), }) } diff --git a/packages/opencode/test/provider/digitalocean.test.ts b/packages/opencode/test/provider/digitalocean.test.ts new file mode 100644 index 000000000..6515ea970 --- /dev/null +++ b/packages/opencode/test/provider/digitalocean.test.ts @@ -0,0 +1,144 @@ +import { test, expect, afterEach } from "bun:test" +import path from "path" + +import { tmpdir } from "../fixture/fixture" +import { WithInstance } from "../../src/project/with-instance" +import { Provider } from "../../src/provider/provider" +import { ProviderID } from "../../src/provider/schema" +import { Env } from "../../src/env" +import { Effect } from "effect" +import { AppRuntime } from "../../src/effect/app-runtime" +import { makeRuntime } from "../../src/effect/run-service" + +const envRuntime = makeRuntime(Env.Service, Env.defaultLayer) +const set = (k: string, v: string) => envRuntime.runSync((svc) => svc.set(k, v)) + +async function list() { + return AppRuntime.runPromise( + Effect.gen(function* () { + const provider = yield* Provider.Service + return yield* provider.list() + }), + ) +} + +const DIGITALOCEAN = ProviderID.make("digitalocean") + +const originalAuthContent = process.env.OPENCODE_AUTH_CONTENT +afterEach(() => { + if (originalAuthContent === undefined) delete process.env.OPENCODE_AUTH_CONTENT + else process.env.OPENCODE_AUTH_CONTENT = originalAuthContent +}) + +function injectAuth(metadata: Record | undefined) { + process.env.OPENCODE_AUTH_CONTENT = JSON.stringify({ + digitalocean: { + type: "api", + key: "sk_do_test", + ...(metadata ? { metadata } : {}), + }, + }) +} + +test("digitalocean provider autoloads from DIGITALOCEAN_ACCESS_TOKEN", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ $schema: "https://opencode.ai/config.json" }), + ) + }, + }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + set("DIGITALOCEAN_ACCESS_TOKEN", "test-token") + const providers = await list() + expect(providers[DIGITALOCEAN]).toBeDefined() + expect(providers[DIGITALOCEAN].source).toBe("env") + const baseModel = Object.values(providers[DIGITALOCEAN].models)[0] + expect(baseModel.api.url).toBe("https://inference.do-ai.run/v1") + expect(baseModel.api.npm).toBe("@ai-sdk/openai-compatible") + const routerEntries = Object.keys(providers[DIGITALOCEAN].models).filter((id) => id.startsWith("router:")) + expect(routerEntries.length).toBe(0) + }, + }) +}) + +test("digitalocean provider.models surfaces cached routers from auth metadata", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ $schema: "https://opencode.ai/config.json" }), + ) + }, + }) + injectAuth({ + routers: JSON.stringify([ + { name: "my-router", uuid: "11f1499a-aaaa-bbbb-cccc-4e013e2ddde4" }, + { name: "other-router", uuid: "22f1499a-aaaa-bbbb-cccc-4e013e2ddde4" }, + ]), + routers_fetched_at: String(Date.now()), + oauth_access: "doo_v1_test", + oauth_expires: String(Date.now() + 60 * 60 * 1000), + }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const providers = await list() + const models = providers[DIGITALOCEAN].models + expect(models["router:my-router"]).toBeDefined() + expect(models["router:my-router"].api.id).toBe("router:my-router") + expect(models["router:my-router"].api.url).toBe("https://inference.do-ai.run/v1") + expect(models["router:my-router"].api.npm).toBe("@ai-sdk/openai-compatible") + expect(models["router:other-router"]).toBeDefined() + }, + }) +}) + +test("digitalocean provider.models skips refresh when oauth bearer is expired", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ $schema: "https://opencode.ai/config.json" }), + ) + }, + }) + injectAuth({ + routers: JSON.stringify([{ name: "stale-router", uuid: "stale" }]), + routers_fetched_at: "0", + oauth_access: "doo_v1_expired", + oauth_expires: "1", + }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const providers = await list() + const models = providers[DIGITALOCEAN].models + expect(models["router:stale-router"]).toBeDefined() + }, + }) +}) + +test("digitalocean provider.models passes through base models when no auth metadata", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ $schema: "https://opencode.ai/config.json" }), + ) + }, + }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + set("DIGITALOCEAN_ACCESS_TOKEN", "test-token") + const providers = await list() + const models = providers[DIGITALOCEAN].models + expect(Object.keys(models).length).toBeGreaterThan(0) + expect(Object.keys(models).filter((id) => id.startsWith("router:")).length).toBe(0) + }, + }) +}) diff --git a/packages/opencode/test/tool/fixtures/models-api.json b/packages/opencode/test/tool/fixtures/models-api.json index 5a3eb7e80..7ced5ca5d 100644 --- a/packages/opencode/test/tool/fixtures/models-api.json +++ b/packages/opencode/test/tool/fixtures/models-api.json @@ -1,4 +1,28 @@ { + "digitalocean": { + "id": "digitalocean", + "env": ["DIGITALOCEAN_ACCESS_TOKEN"], + "npm": "@ai-sdk/openai-compatible", + "api": "https://inference.do-ai.run/v1", + "name": "DigitalOcean", + "doc": "https://docs.digitalocean.com/products/genai-platform/", + "models": { + "openai-gpt-oss-120b": { + "id": "openai-gpt-oss-120b", + "name": "GPT OSS 120B", + "attachment": false, + "reasoning": false, + "tool_call": true, + "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 } + } + } + }, "ollama-cloud": { "id": "ollama-cloud", "env": ["OLLAMA_API_KEY"], diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index 2e96dd980..6156477be 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -8,10 +8,9 @@ import type { UserMessage, Message, Part, - Auth, Config as SDKConfig, } from "@opencode-ai/sdk" -import type { Provider as ProviderV2, Model as ModelV2 } from "@opencode-ai/sdk/v2" +import type { Provider as ProviderV2, Model as ModelV2, Auth } from "@opencode-ai/sdk/v2" import type { BunShell } from "./shell.js" import { type ToolDefinition } from "./tool.js" @@ -153,6 +152,7 @@ export type AuthHook = { type: "success" key: string provider?: string + metadata?: Record } | { type: "failed" @@ -177,7 +177,7 @@ export type AuthOAuthResult = { url: string; instructions: string } & ( accountId?: string enterpriseUrl?: string } - | { key: string } + | { key: string; metadata?: Record } )) | { type: "failed" @@ -198,7 +198,7 @@ export type AuthOAuthResult = { url: string; instructions: string } & ( accountId?: string enterpriseUrl?: string } - | { key: string } + | { key: string; metadata?: Record } )) | { type: "failed" diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 8fd2a02b9..5e4fd8906 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1666,6 +1666,9 @@ export type OAuth = { export type ApiAuth = { type: "api" key: string + metadata?: { + [key: string]: string + } } export type WellKnownAuth = { diff --git a/packages/ui/src/assets/icons/provider/digitalocean.svg b/packages/ui/src/assets/icons/provider/digitalocean.svg new file mode 100644 index 000000000..5be390b9d --- /dev/null +++ b/packages/ui/src/assets/icons/provider/digitalocean.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/src/components/provider-icons/sprite.svg b/packages/ui/src/components/provider-icons/sprite.svg index a0214b40d..68b99ce56 100644 --- a/packages/ui/src/components/provider-icons/sprite.svg +++ b/packages/ui/src/components/provider-icons/sprite.svg @@ -854,6 +854,20 @@ d="M79.01 5.863c-4.066 0-6.511 2.92-6.511 6.535 0 3.635 2.445 6.555 6.511 6.555 4.046 0 6.512-2.92 6.512-6.555s-2.466-6.535-6.512-6.535Zm0 10.968c-2.633 0-4.172-1.933-4.172-4.433s1.539-4.455 4.172-4.455c2.635 0 4.151 1.933 4.151 4.434 0 2.521-1.516 4.454-4.15 4.454Zm14.393 2.096c3.393 0 5.542-1.808 5.837-4.539h-2.36c-.316 1.555-1.517 2.437-3.477 2.437-2.423 0-3.878-1.68-3.878-4.433 0-2.774 1.476-4.434 3.878-4.434 1.96 0 3.14.862 3.477 2.5h2.36c-.295-2.773-2.444-4.622-5.837-4.622-3.856 0-6.217 2.669-6.217 6.535 0 3.887 2.36 6.556 6.217 6.556Zm-29.543-.311h2.36v-6.01c0-2.752 1.348-4.244 3.772-4.244h2.276V6.177h-2.255c-2.128 0-3.288.735-3.898 2.605l-.443-.063.527-2.542h-2.36v12.439h.02Zm-24.445-7.332c.106-2.101 1.517-3.53 3.793-3.53 2.276 0 3.646 1.345 3.646 3.53h-7.439Zm9.778.4c0-3.426-2.381-5.821-5.943-5.821-3.73 0-6.174 2.563-6.174 6.535 0 4.013 2.423 6.555 6.28 6.555 2.929 0 5.247-1.597 5.669-3.887h-2.36c-.507 1.156-1.666 1.828-3.31 1.828-2.38 0-3.877-1.408-3.94-3.803h9.694c.042-.588.084-.861.084-1.408Zm5.69 6.932h1.939l5.5-12.44h-2.529L56 15.99l-.316.021-3.793-9.833h-2.508l5.5 12.439ZM32.23 12.35c0-.882-.359-1.701-.99-2.437a8.594 8.594 0 0 1-1.497 1.093c.337.42.527.861.527 1.345 0 2.731-5.837 4.811-14.14 4.811-8.281.021-14.118-2.059-14.118-4.811 0-.463.168-.925.505-1.345a8.13 8.13 0 0 1-1.475-1.093c-.632.736-.99 1.555-.99 2.438 0 4.034 7.207 6.534 16.1 6.534 8.87.021 16.078-2.5 16.078-6.535Zm-3.351 1.534c-.906-.462-1.96-.861-3.16-1.197-1.37.378-2.909.672-4.553.861 2.318.294 4.341.778 5.9 1.408.76-.336 1.37-.693 1.813-1.072Zm-17.849-.357a31.902 31.902 0 0 1-4.467-.84c-1.18.336-2.255.735-3.16 1.197.42.379 1.01.715 1.748 1.05 1.539-.63 3.52-1.113 5.88-1.407Zm21.2-6.808c0-4.013-7.207-6.534-16.079-6.534C7.26.185.051 2.706.051 6.719c0 4.035 7.208 6.535 16.1 6.535 8.872.021 16.079-2.5 16.079-6.535Zm-1.94 0c0 2.732-5.836 4.812-14.139 4.812-8.302.021-14.14-2.06-14.14-4.812 0-2.731 5.838-4.811 14.14-4.811 7.86 0 14.14 2.08 14.14 4.811Zm-3.223 2.564c.758-.336 1.37-.694 1.812-1.072-2.95-1.513-7.544-2.353-12.728-2.353s-9.799.84-12.728 2.353c.422.378 1.012.715 1.75 1.05 2.507-1.05 6.363-1.68 10.978-1.68 4.404 0 8.324.651 10.916 1.702ZM1.042 15.628c-.632.736-.99 1.534-.99 2.438 0 4.034 7.207 6.534 16.1 6.534 8.892 0 16.099-2.521 16.099-6.534 0-.883-.359-1.702-.99-2.438-.422.4-.907.757-1.497 1.093.337.42.527.861.527 1.345 0 2.731-5.837 4.811-14.14 4.811-8.302 0-14.14-2.08-14.14-4.811 0-.463.17-.925.506-1.345a10.73 10.73 0 0 1-1.475-1.093Z" > + + + + + + ` in your DigitalOcean account. You can rotate or revoke it from the **Model Access Keys** page in the "Manage" section of the DigitalOcean console under Inference. + ::: + +4. Run the `/models` command. Your Inference Routers appear as the format `router:` in the model selection. + + ```txt + /models + ``` + +5. To pick up newly created Inference Routers, re-run `/connect` and select **DigitalOcean** again. + +#### Using a Model Access Key + +If you'd rather paste a key directly: + +1. Head over to the **Manage** page in the Inference section of the [DigitalOcean console](https://cloud.digitalocean.com/) and create a new key. + +2. Run the `/connect` command and select **DigitalOcean**, then **Paste Model Access Key**. + + ```txt + ┌ Enter your DigitalOcean Model Access Key + │ + │ + └ enter + ``` + + :::note + Inference Routers are not auto-discovered with this method. To surface them in the model picker, sign in via OAuth instead. + ::: + +3. Run the `/models` command to select a model. + + ```txt + /models + ``` + +#### Environment Variable + +Alternatively, set your Model Access Key as an environment variable. + +```bash frame="none" +export DIGITALOCEAN_ACCESS_TOKEN=your-model-access-key +``` + +#### Inference Routers + +Inference Routers let you define a routing policy across multiple models — picking the cheapest, fastest, or most appropriate model per request based on the task. After OAuth, OpenCode surfaces each router as `router:` in the model picker. + +Selecting a router model is a drop-in replacement for any other model — OpenCode forwards your request and DigitalOcean picks the underlying model based on your router's policy. Learn more about [Inference Routers](https://docs.digitalocean.com/products/inference/how-to/use-inference-router/) + +--- + ### FrogBot 1. Head over to the [FrogBot dashboard](https://app.frogbot.ai/signup), create an account, and generate an API key. From cb511f78ffb90899a1b79f7bc130264b52674299 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 12 May 2026 16:23:15 -0500 Subject: [PATCH 148/378] fix(plugin): preserve tool attachments (#27157) --- packages/opencode/src/tool/registry.ts | 4 +- packages/opencode/test/tool/registry.test.ts | 50 ++++++++++++++++++++ packages/plugin/src/tool.ts | 16 ++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index f72f10dd1..7de3c8f4e 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -160,11 +160,13 @@ export const layer: Layer.Layer< const result = yield* Effect.promise(() => def.execute(args as any, pluginCtx)) const output = typeof result === "string" ? result : result.output const metadata = typeof result === "string" ? {} : (result.metadata ?? {}) + const attachments = typeof result === "string" ? undefined : result.attachments const info = yield* agent.get(toolCtx.agent) const out = yield* truncate.output(output, {}, info) return { - title: "", + title: typeof result === "string" ? "" : (result.title ?? ""), output: out.truncated ? out.content : output, + attachments, metadata: { ...metadata, truncated: out.truncated, diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index fb4dd31a5..595fcd808 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -5,6 +5,7 @@ import { pathToFileURL } from "url" import { Effect, Layer, Result, Schema } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { ToolRegistry } from "@/tool/registry" +import { Tool } from "@/tool/tool" import { Flag } from "@opencode-ai/core/flag/flag" import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" @@ -29,6 +30,7 @@ import { InstanceState } from "@/effect/instance-state" import { Reference } from "@/reference/reference" import { ProviderID, ModelID } from "@/provider/schema" import { ToolJsonSchema } from "@/tool/json-schema" +import { MessageID, SessionID } from "@/session/schema" const node = CrossSpawnSpawner.defaultLayer const originalExperimentalScout = Flag.OPENCODE_EXPERIMENTAL_SCOUT @@ -193,6 +195,54 @@ describe("tool.registry", () => { }), ) + it.instance("preserves attachments from structured custom tool results", () => + Effect.gen(function* () { + const test = yield* TestInstance + const customTools = path.join(test.directory, ".opencode", "tools") + const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href + yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true })) + yield* Effect.promise(() => + Bun.write( + path.join(customTools, "image.ts"), + [ + `import { tool } from ${JSON.stringify(pluginTool)}`, + "export default tool({", + " description: 'image tool',", + " args: {},", + " execute: async () => ({", + " output: 'here is an image',", + " attachments: [{ type: 'file', mime: 'image/png', filename: 'picture.png', url: 'data:image/png;base64,AAAA' }],", + " }),", + "})", + "", + ].join("\n"), + ), + ) + + const registry = yield* ToolRegistry.Service + const loaded = (yield* registry.all()).find((tool) => tool.id === "image") + if (!loaded) throw new Error("custom image tool was not loaded") + const agents = yield* Agent.Service + const result = yield* loaded.execute( + {}, + { + sessionID: SessionID.make("ses_test"), + messageID: MessageID.make("msg_test"), + agent: (yield* agents.defaultInfo()).name, + abort: new AbortController().signal, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + } satisfies Tool.Context, + ) + + expect(result.output).toBe("here is an image") + expect(result.attachments).toEqual([ + { type: "file", mime: "image/png", filename: "picture.png", url: "data:image/png;base64,AAAA" }, + ]) + }), + ) + it.instance("loads legacy JSON-schema-shaped custom tools with wire schema", () => Effect.gen(function* () { const test = yield* TestInstance diff --git a/packages/plugin/src/tool.ts b/packages/plugin/src/tool.ts index 3105bf534..b8a634c79 100644 --- a/packages/plugin/src/tool.ts +++ b/packages/plugin/src/tool.ts @@ -27,7 +27,21 @@ type AskInput = { metadata: { [key: string]: any } } -export type ToolResult = string | { output: string; metadata?: { [key: string]: any } } +export type ToolAttachment = { + type: "file" + mime: string + url: string + filename?: string +} + +export type ToolResult = + | string + | { + title?: string + output: string + metadata?: { [key: string]: any } + attachments?: ToolAttachment[] + } export function tool(input: { description: string From 2cb697b72050c1f7a9661be59c2d610e6ae81d89 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 21:24:39 +0000 Subject: [PATCH 149/378] chore: generate --- packages/opencode/src/plugin/digitalocean.ts | 8 +++++-- .../test/provider/digitalocean.test.ts | 20 ++++-------------- packages/opencode/test/tool/registry.test.ts | 21 ++++++++----------- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/packages/opencode/src/plugin/digitalocean.ts b/packages/opencode/src/plugin/digitalocean.ts index 31656656f..fa4adf633 100644 --- a/packages/opencode/src/plugin/digitalocean.ts +++ b/packages/opencode/src/plugin/digitalocean.ts @@ -246,7 +246,9 @@ async function createModelAccessKey(bearer: string): Promise { return data.api_key_info } -async function listRouters(bearer: string): Promise<{ ok: true; routers: RouterEntry[] } | { ok: false; status: number }> { +async function listRouters( + bearer: string, +): Promise<{ ok: true; routers: RouterEntry[] } | { ok: false; status: number }> { const res = await fetch(`${DO_API_BASE}/v2/gen-ai/models/routers`, { headers: { Authorization: `Bearer ${bearer}`, @@ -293,7 +295,9 @@ function parseRoutersJSON(raw: string | undefined): RouterEntry[] { try { const parsed = JSON.parse(raw) if (!Array.isArray(parsed)) return [] - return parsed.flatMap((r) => (r && typeof r.name === "string" ? [{ name: r.name, uuid: r.uuid, description: r.description }] : [])) + return parsed.flatMap((r) => + r && typeof r.name === "string" ? [{ name: r.name, uuid: r.uuid, description: r.description }] : [], + ) } catch { return [] } diff --git a/packages/opencode/test/provider/digitalocean.test.ts b/packages/opencode/test/provider/digitalocean.test.ts index 6515ea970..6fc49a6ef 100644 --- a/packages/opencode/test/provider/digitalocean.test.ts +++ b/packages/opencode/test/provider/digitalocean.test.ts @@ -43,10 +43,7 @@ function injectAuth(metadata: Record | undefined) { test("digitalocean provider autoloads from DIGITALOCEAN_ACCESS_TOKEN", async () => { await using tmp = await tmpdir({ init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ $schema: "https://opencode.ai/config.json" }), - ) + await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) }, }) await WithInstance.provide({ @@ -68,10 +65,7 @@ test("digitalocean provider autoloads from DIGITALOCEAN_ACCESS_TOKEN", async () test("digitalocean provider.models surfaces cached routers from auth metadata", async () => { await using tmp = await tmpdir({ init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ $schema: "https://opencode.ai/config.json" }), - ) + await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) }, }) injectAuth({ @@ -100,10 +94,7 @@ test("digitalocean provider.models surfaces cached routers from auth metadata", test("digitalocean provider.models skips refresh when oauth bearer is expired", async () => { await using tmp = await tmpdir({ init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ $schema: "https://opencode.ai/config.json" }), - ) + await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) }, }) injectAuth({ @@ -125,10 +116,7 @@ test("digitalocean provider.models skips refresh when oauth bearer is expired", test("digitalocean provider.models passes through base models when no auth metadata", async () => { await using tmp = await tmpdir({ init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ $schema: "https://opencode.ai/config.json" }), - ) + await Bun.write(path.join(dir, "opencode.json"), JSON.stringify({ $schema: "https://opencode.ai/config.json" })) }, }) await WithInstance.provide({ diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 595fcd808..b2beda70c 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -223,18 +223,15 @@ describe("tool.registry", () => { const loaded = (yield* registry.all()).find((tool) => tool.id === "image") if (!loaded) throw new Error("custom image tool was not loaded") const agents = yield* Agent.Service - const result = yield* loaded.execute( - {}, - { - sessionID: SessionID.make("ses_test"), - messageID: MessageID.make("msg_test"), - agent: (yield* agents.defaultInfo()).name, - abort: new AbortController().signal, - messages: [], - metadata: () => Effect.void, - ask: () => Effect.void, - } satisfies Tool.Context, - ) + const result = yield* loaded.execute({}, { + sessionID: SessionID.make("ses_test"), + messageID: MessageID.make("msg_test"), + agent: (yield* agents.defaultInfo()).name, + abort: new AbortController().signal, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + } satisfies Tool.Context) expect(result.output).toBe("here is an image") expect(result.attachments).toEqual([ From 65368f609d851a9e832d8dd176903df9a690a635 Mon Sep 17 00:00:00 2001 From: Andrew Suffield Date: Tue, 12 May 2026 18:09:06 -0400 Subject: [PATCH 150/378] fix: preserve permission ordering by accepting a layered array (#23214) Co-authored-by: Andrew Suffield Co-authored-by: Aiden Cline --- packages/opencode/src/agent/agent.ts | 6 +- packages/opencode/src/config/config.ts | 22 ++- packages/opencode/src/config/permission.ts | 8 + packages/opencode/test/config/config.test.ts | 178 +++++++++++++++++- .../opencode/test/permission-task.test.ts | 25 ++- 5 files changed, 221 insertions(+), 18 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 423a51318..74ca1a402 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -1,4 +1,5 @@ import { Config } from "@/config/config" +import { ConfigPermission } from "@/config/permission" import { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "../provider/schema" import { generateObject, streamObject, type ModelMessage } from "ai" @@ -117,7 +118,10 @@ export const layer = Layer.effect( }, }) - const user = Permission.fromConfig(cfg.permission ?? {}) + // Convert permission layers to rulesets and merge them + // Each layer's rules come after the previous, so later configs override earlier ones + const layers = ConfigPermission.toLayers(cfg.permission) + const user = Permission.merge(...layers.map((p) => Permission.fromConfig(p))) const agents: Record = { build: { diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 545e48e64..91eeab47e 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -55,6 +55,13 @@ function mergeConfigConcatArrays(target: Info, source: Info): Info { if (target.instructions && source.instructions) { merged.instructions = Array.from(new Set([...target.instructions, ...source.instructions])) } + // Accumulate permission layers for later merging as rulesets. + // This preserves the ordering semantics: later rules override earlier rules. + // Each layer keeps the raw shape the user wrote on disk; consumers should use + // ConfigPermission.toLayers to normalise. + if (source.permission) { + merged.permission = [...ConfigPermission.toLayers(target.permission), ...ConfigPermission.toLayers(source.permission)] + } return merged } @@ -228,7 +235,12 @@ export const Info = Schema.Struct({ description: "Additional instruction files or patterns to include", }), layout: Schema.optional(ConfigLayout.Layout).annotate({ description: "@deprecated Always uses stretch layout." }), - permission: Schema.optional(ConfigPermission.Info), + permission: Schema.optional( + Schema.Union([ConfigPermission.Info, Schema.mutable(Schema.Array(ConfigPermission.Info))]), + ).annotate({ + description: + "Permission configuration. Accepts a single object (per-tool action map) or an array of layered configs; arrays are merged in order so later layers override earlier ones.", + }), tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), attachment: Schema.optional(ConfigAttachment.Info).annotate({ description: "Attachment processing configuration, including image size limits and resizing behavior", @@ -704,11 +716,12 @@ export const layer = Layer.effect( } if (Flag.OPENCODE_PERMISSION) { - result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION)) + const envPermission = JSON.parse(Flag.OPENCODE_PERMISSION) as ConfigPermission.Info + result.permission = [...ConfigPermission.toLayers(result.permission), envPermission] } if (result.tools) { - const perms: Record = {} + const perms: ConfigPermission.Info = {} for (const [tool, enabled] of Object.entries(result.tools)) { const action: ConfigPermission.Action = enabled ? "allow" : "deny" if (tool === "write" || tool === "edit" || tool === "patch") { @@ -717,7 +730,8 @@ export const layer = Layer.effect( } perms[tool] = action } - result.permission = mergeDeep(perms, result.permission ?? {}) + // Tools permissions come before other permissions (they can be overridden) + result.permission = [perms, ...ConfigPermission.toLayers(result.permission)] } if (!result.username) result.username = os.userInfo().username diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts index 1092ae2b7..c780d2443 100644 --- a/packages/opencode/src/config/permission.ts +++ b/packages/opencode/src/config/permission.ts @@ -56,3 +56,11 @@ export const Info = InputSchema.pipe( ).annotate({ identifier: "PermissionConfig" }) type _Info = Schema.Schema.Type export type Info = { -readonly [K in keyof _Info]: _Info[K] } + +// Top-level config accepts either a single permission object or an array of +// layered configs. Internal merging produces arrays; this helper normalises +// either shape into the array form expected by consumers. +export function toLayers(value: Info | Info[] | undefined): Info[] { + if (!value) return [] + return Array.isArray(value) ? value : [value] +} diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 90e78efcd..a2e439177 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -3,7 +3,9 @@ import { Effect, Layer, Option } from "effect" import { NodeFileSystem, NodePath } from "@effect/platform-node" import { Config } from "@/config/config" import { ConfigManaged } from "@/config/managed" +import { ConfigPermission } from "@/config/permission" import { ConfigParse } from "../../src/config/parse" +import { Permission } from "../../src/permission" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { Instance } from "../../src/project/instance" @@ -276,6 +278,40 @@ test("updates global config and omits empty shell key in json", async () => { } }) +test("global config update preserves single-object permission shape on disk", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + shell: "bash", + permission: { bash: "ask" }, + }), + ) + }, + }) + + const prev = Global.Path.config + ;(Global.Path as { config: string }).config = tmp.path + await clear(true) + + try { + // Updating an unrelated key must not rewrite `permission` from object to array form. + await saveGlobal({ shell: "zsh" }) + + const written = await Filesystem.readJson<{ permission?: unknown; shell?: string }>( + path.join(tmp.path, "opencode.json"), + ) + expect(written.shell).toBe("zsh") + expect(Array.isArray(written.permission)).toBe(false) + expect(written.permission).toEqual({ bash: "ask" }) + } finally { + ;(Global.Path as { config: string }).config = prev + await clear(true) + } +}) + test("updates global config and omits empty shell key in jsonc", async () => { await using tmp = await tmpdir({ init: async (dir) => { @@ -1713,7 +1749,10 @@ test("permission config preserves user key order", async () => { directory: tmp.path, fn: async () => { const config = await load() - expect(Object.keys(config.permission!)).toEqual([ + // load() goes through the merge pipeline, producing the layered array form + expect(config.permission).toHaveLength(1) + const perm = (config.permission as ConfigPermission.Info[])[0] + expect(Object.keys(perm)).toEqual([ "*", "edit", "write", @@ -1729,6 +1768,129 @@ test("permission config preserves user key order", async () => { }) }) +// Global bash "rm *" deny is inherited, but user's top-level "*" ask comes after and overrides it +test("user top-level catchall overrides inherited bash rules", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + permission: { + bash: { "rm *": "deny" }, + }, + }), + ) + const opencodeDir = path.join(dir, ".opencode") + await fs.mkdir(opencodeDir, { recursive: true }) + await Filesystem.write( + path.join(opencodeDir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + permission: { + "*": "ask", + bash: { "ls *": "allow" }, + }, + }), + ) + }, + }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const config = await load() + const layers = ConfigPermission.toLayers(config.permission) + const ruleset = Permission.merge(...layers.map((p) => Permission.fromConfig(p))) + + expect(Permission.evaluate("bash", "rm -rf /", ruleset).action).toBe("ask") + expect(Permission.evaluate("bash", "ls -la", ruleset).action).toBe("allow") + expect(Permission.evaluate("bash", "echo hello", ruleset).action).toBe("ask") + }, + }) +}) + +// No top-level catchall, so global bash "rm *" deny is preserved +test("inherited bash rules apply when no user top-level catchall", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + permission: { + bash: { "rm *": "deny" }, + }, + }), + ) + const opencodeDir = path.join(dir, ".opencode") + await fs.mkdir(opencodeDir, { recursive: true }) + await Filesystem.write( + path.join(opencodeDir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + permission: { + bash: { "ls *": "allow" }, + }, + }), + ) + }, + }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const config = await load() + const layers = ConfigPermission.toLayers(config.permission) + const ruleset = Permission.merge(...layers.map((p) => Permission.fromConfig(p))) + + expect(Permission.evaluate("bash", "rm -rf /", ruleset).action).toBe("deny") + expect(Permission.evaluate("bash", "ls -la", ruleset).action).toBe("allow") + }, + }) +}) + +// User's bash "*" catchall overrides global "rm *" deny +test("user bash catchall overrides inherited bash rules", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + permission: { + bash: { "rm *": "deny" }, + }, + }), + ) + const opencodeDir = path.join(dir, ".opencode") + await fs.mkdir(opencodeDir, { recursive: true }) + await Filesystem.write( + path.join(opencodeDir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + permission: { + bash: { "*": "ask", "ls *": "allow" }, + }, + }), + ) + }, + }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const config = await load() + const layers = ConfigPermission.toLayers(config.permission) + const ruleset = Permission.merge(...layers.map((p) => Permission.fromConfig(p))) + + expect(Permission.evaluate("bash", "rm -rf /", ruleset).action).toBe("ask") + expect(Permission.evaluate("bash", "ls -la", ruleset).action).toBe("allow") + expect(Permission.evaluate("bash", "echo hello", ruleset).action).toBe("ask") + + // Non-bash permissions should use the top-level "*" rule + expect(Permission.evaluate("read", "foo.txt", ruleset).action).toBe("ask") + }, + }) +}) + test("config parser preserves permission order while rejecting unknown top-level keys", () => { const config = ConfigParse.schema( Config.Info, @@ -1742,7 +1904,8 @@ test("config parser preserves permission order while rejecting unknown top-level "test", ) - expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"]) + // ConfigParse.schema preserves the raw shape the user wrote + expect(Object.keys(config.permission as ConfigPermission.Info)).toEqual(["bash", "*", "edit"]) try { ConfigParse.schema(Config.Info, { invalid_field: true }, "test") throw new Error("expected config parse to fail") @@ -2579,11 +2742,12 @@ test("parseManagedPlist parses permission rules", async () => { ), "test:mobileconfig", ) - expect(config.permission?.["*"]).toBe("ask") - expect(config.permission?.grep).toBe("allow") - expect(config.permission?.webfetch).toBe("ask") - expect(config.permission?.["~/.ssh/*"]).toBe("deny") - const bash = config.permission?.bash as Record + const perm = config.permission as ConfigPermission.Info + expect(perm?.["*"]).toBe("ask") + expect(perm?.grep).toBe("allow") + expect(perm?.webfetch).toBe("ask") + expect(perm?.["~/.ssh/*"]).toBe("deny") + const bash = perm?.bash as Record expect(bash?.["rm -rf *"]).toBe("deny") expect(bash?.["curl *"]).toBe("deny") }) diff --git a/packages/opencode/test/permission-task.test.ts b/packages/opencode/test/permission-task.test.ts index 64b93bb8b..77ceda8a4 100644 --- a/packages/opencode/test/permission-task.test.ts +++ b/packages/opencode/test/permission-task.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, test, expect } from "bun:test" import { Permission } from "../src/permission" import { Config } from "@/config/config" +import { ConfigPermission } from "@/config/permission" import { Instance } from "../src/project/instance" import { WithInstance } from "../src/project/with-instance" import { disposeAllInstances, tmpdir } from "./fixture/fixture" @@ -163,7 +164,9 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.fromConfig(config.permission ?? {}) + const ruleset = Permission.merge( + ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), + ) // general and orchestrator-fast should be allowed, code-reviewer denied expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") expect(Permission.evaluate("task", "orchestrator-fast", ruleset).action).toBe("allow") @@ -188,7 +191,9 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.fromConfig(config.permission ?? {}) + const ruleset = Permission.merge( + ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), + ) // general and code-reviewer should be ask, orchestrator-* denied expect(Permission.evaluate("task", "general", ruleset).action).toBe("ask") expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("ask") @@ -213,7 +218,9 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.fromConfig(config.permission ?? {}) + const ruleset = Permission.merge( + ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), + ) expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") expect(Permission.evaluate("task", "code-reviewer", ruleset).action).toBe("deny") // Unspecified agents default to "ask" @@ -240,7 +247,9 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.fromConfig(config.permission ?? {}) + const ruleset = Permission.merge( + ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), + ) // Verify task permissions expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") @@ -278,7 +287,9 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.fromConfig(config.permission ?? {}) + const ruleset = Permission.merge( + ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), + ) // Last matching rule wins - "*" deny is last, so all agents are denied expect(Permission.evaluate("task", "general", ruleset).action).toBe("deny") @@ -309,7 +320,9 @@ describe("permission.task with real config files", () => { directory: tmp.path, fn: async () => { const config = await load() - const ruleset = Permission.fromConfig(config.permission ?? {}) + const ruleset = Permission.merge( + ...ConfigPermission.toLayers(config.permission).map((p) => Permission.fromConfig(p)), + ) // Evaluate uses findLast - "general" allow comes after "*" deny expect(Permission.evaluate("task", "general", ruleset).action).toBe("allow") From baef5cd43ba1abb921918708d56ccbd08ebeaaec Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 22:11:13 +0000 Subject: [PATCH 151/378] chore: generate --- packages/opencode/src/config/config.ts | 5 ++++- packages/sdk/js/src/v2/gen/types.gen.ts | 5 ++++- packages/sdk/openapi.json | 13 ++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 91eeab47e..78bb83ed8 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -60,7 +60,10 @@ function mergeConfigConcatArrays(target: Info, source: Info): Info { // Each layer keeps the raw shape the user wrote on disk; consumers should use // ConfigPermission.toLayers to normalise. if (source.permission) { - merged.permission = [...ConfigPermission.toLayers(target.permission), ...ConfigPermission.toLayers(source.permission)] + merged.permission = [ + ...ConfigPermission.toLayers(target.permission), + ...ConfigPermission.toLayers(source.permission), + ] } return merged } diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index f062700b7..b42935519 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1263,7 +1263,10 @@ export type Config = { } instructions?: Array layout?: LayoutConfig - permission?: PermissionConfig + /** + * Permission configuration. Accepts a single object (per-tool action map) or an array of layered configs; arrays are merged in order so later layers override earlier ones. + */ + permission?: PermissionConfig | Array tools?: { [key: string]: boolean } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 7005382f6..eed0282fc 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -12407,7 +12407,18 @@ "$ref": "#/components/schemas/LayoutConfig" }, "permission": { - "$ref": "#/components/schemas/PermissionConfig" + "anyOf": [ + { + "$ref": "#/components/schemas/PermissionConfig" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionConfig" + } + } + ], + "description": "Permission configuration. Accepts a single object (per-tool action map) or an array of layered configs; arrays are merged in order so later layers override earlier ones." }, "tools": { "type": "object", From 81dd46abec087838b21df8b97d0ecd87dd073d8f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:45:01 -0400 Subject: [PATCH 152/378] test: migrate websearch tests to effect runner (#27170) --- packages/opencode/test/tool/websearch.test.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/opencode/test/tool/websearch.test.ts b/packages/opencode/test/tool/websearch.test.ts index 591b385fd..b8edc2dc2 100644 --- a/packages/opencode/test/tool/websearch.test.ts +++ b/packages/opencode/test/tool/websearch.test.ts @@ -4,6 +4,7 @@ import { parseResponse } from "../../src/tool/mcp-websearch" import { selectWebSearchProvider, webSearchModelName, webSearchProviderLabel } from "../../src/tool/websearch" import { ProviderID } from "../../src/provider/schema" import { webSearchEnabled } from "../../src/tool/registry" +import { it } from "../lib/effect" const SESSION_ID = "ses_0196aabbccddeeff001122334455" @@ -74,17 +75,24 @@ describe("websearch MCP response parser", () => { }, }) - test("parses plain JSON-RPC responses", async () => { - await expect(Effect.runPromise(parseResponse(payload))).resolves.toBe("search results") - }) + it.effect("parses plain JSON-RPC responses", () => + Effect.gen(function* () { + const result = yield* parseResponse(payload) + expect(result).toBe("search results") + }), + ) - test("parses SSE JSON-RPC responses", async () => { - await expect(Effect.runPromise(parseResponse(`event: message\ndata: ${payload}\n\n`))).resolves.toBe( - "search results", - ) - }) + it.effect("parses SSE JSON-RPC responses", () => + Effect.gen(function* () { + const result = yield* parseResponse(`event: message\ndata: ${payload}\n\n`) + expect(result).toBe("search results") + }), + ) - test("ignores non-JSON SSE data frames", async () => { - await expect(Effect.runPromise(parseResponse(`data: [DONE]\ndata: ${payload}\n\n`))).resolves.toBe("search results") - }) + it.effect("ignores non-JSON SSE data frames", () => + Effect.gen(function* () { + const result = yield* parseResponse(`data: [DONE]\ndata: ${payload}\n\n`) + expect(result).toBe("search results") + }), + ) }) From 2c334d92420da3e7c5760214edd5aa8299e57022 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:46:19 -0400 Subject: [PATCH 153/378] Migrate schema error body tests to Effect runner (#27172) --- .../server/httpapi-schema-error-body.test.ts | 136 ++++++++---------- 1 file changed, 63 insertions(+), 73 deletions(-) diff --git a/packages/opencode/test/server/httpapi-schema-error-body.test.ts b/packages/opencode/test/server/httpapi-schema-error-body.test.ts index fe6a1caad..48ed7b6bf 100644 --- a/packages/opencode/test/server/httpapi-schema-error-body.test.ts +++ b/packages/opencode/test/server/httpapi-schema-error-body.test.ts @@ -3,7 +3,6 @@ import { Effect } from "effect" import { eq } from "drizzle-orm" import * as Database from "@/storage/db" import { ModelID, ProviderID } from "../../src/provider/schema" -import { WithInstance } from "../../src/project/with-instance" import { Server } from "../../src/server/server" import { Session } from "@/session/session" import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session" @@ -11,80 +10,68 @@ import { SyncPaths } from "../../src/server/routes/instance/httpapi/groups/sync" import { MessageID, PartID } from "../../src/session/schema" import { PartTable } from "@/session/session.sql" import { resetDatabase } from "../fixture/db" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" -import { it } from "../lib/effect" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" + +const it = testEffect(Session.defaultLayer) afterEach(async () => { await disposeAllInstances() await resetDatabase() }) -const withTmp = ( - options: Parameters[0], - fn: (tmp: Awaited>) => Effect.Effect, -) => - Effect.acquireRelease( - Effect.promise(() => tmpdir(options)), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ).pipe(Effect.flatMap(fn)) - -async function seedCorruptStepFinishPart(directory: string) { - return WithInstance.provide({ - directory, - fn: () => - Effect.runPromise( - Effect.gen(function* () { - const session = yield* Session.Service - const info = yield* session.create({}) - const message = yield* session.updateMessage({ - id: MessageID.ascending(), - role: "user", - sessionID: info.id, - agent: "build", - model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, - time: { created: Date.now() }, - }) - const partID = PartID.ascending() - yield* session.updatePart({ - id: partID, - sessionID: info.id, - messageID: message.id, +const seedCorruptStepFinishPart = Effect.gen(function* () { + const session = yield* Session.Service + const info = yield* session.create({}) + const message = yield* session.updateMessage({ + id: MessageID.ascending(), + role: "user", + sessionID: info.id, + agent: "build", + model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, + time: { created: Date.now() }, + }) + const partID = PartID.ascending() + yield* session.updatePart({ + id: partID, + sessionID: info.id, + messageID: message.id, + type: "step-finish", + reason: "stop", + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + }) + // Schema.Finite still rejects NaN at encode: exact mirror of the corrupt row + // that broke the user's session in the OMO/Windows bug. + yield* Effect.sync(() => + Database.use((db) => + db + .update(PartTable) + .set({ + data: { type: "step-finish", reason: "stop", cost: 0, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - }) - // Schema.Finite still rejects NaN at encode — exact mirror of the - // corrupt row that broke the user's session in the OMO/Windows bug. - Database.use((db) => - db - .update(PartTable) - .set({ - data: { - type: "step-finish", - reason: "stop", - cost: 0, - tokens: { input: 0, output: NaN, reasoning: 0, cache: { read: 0, write: 0 } }, - } as never, // drizzle's .set() can't narrow the discriminated union - }) - .where(eq(PartTable.id, partID)) - .run(), - ) - return info.id - }).pipe(Effect.provide(Session.defaultLayer)), - ), - }) -} + tokens: { input: 0, output: NaN, reasoning: 0, cache: { read: 0, write: 0 } }, + } as never, // drizzle's .set() can't narrow the discriminated union + }) + .where(eq(PartTable.id, partID)) + .run(), + ), + ) + return info.id +}) describe("schema-rejection wire shape", () => { - it.live( + it.instance( "Payload schema rejection returns NamedError-shaped JSON, not empty", - withTmp({ git: true, config: { formatter: false, lsp: false } }, (tmp) => + () => Effect.gen(function* () { + const test = yield* TestInstance const res = yield* Effect.promise(async () => Server.Default().app.request(SyncPaths.history, { method: "POST", - headers: { "x-opencode-directory": tmp.path, "content-type": "application/json" }, + headers: { "x-opencode-directory": test.directory, "content-type": "application/json" }, body: JSON.stringify({ aggregate: -1 }), }), ) @@ -99,36 +86,38 @@ describe("schema-rejection wire shape", () => { expect(parsed.data.message).toEqual(expect.any(String)) expect(parsed.data.message.length).toBeGreaterThan(0) }), - ), + { git: true, config: { formatter: false, lsp: false } }, ) - it.live( + it.instance( "Query schema rejection returns NamedError-shaped JSON", - withTmp({ git: true, config: { formatter: false, lsp: false } }, (tmp) => + () => Effect.gen(function* () { + const test = yield* TestInstance // /find/file?limit=999999 violates the limit constraint check. - const url = `/find/file?query=foo&limit=999999&directory=${encodeURIComponent(tmp.path)}` + const url = `/find/file?query=foo&limit=999999&directory=${encodeURIComponent(test.directory)}` const res = yield* Effect.promise(async () => Server.Default().app.request(url)) const body = yield* Effect.promise(async () => res.text()) expect(res.status).toBe(400) const parsed = JSON.parse(body) expect(parsed).toMatchObject({ name: "BadRequest", data: { kind: "Query" } }) }), - ), + { git: true, config: { formatter: false, lsp: false } }, ) - it.live( + it.instance( "rejected request body never echoes back unbounded — message is capped", // Defense against DoS-amplification + secret-echo: Effect's Issue formatter // dumps the rejected `actual` verbatim. A multi-MB invalid array would // become a multi-MB 400 response and log line. Cap kicks in around 1KB. - withTmp({ git: true, config: { formatter: false, lsp: false } }, (tmp) => + () => Effect.gen(function* () { + const test = yield* TestInstance const huge = "X".repeat(50_000) const res = yield* Effect.promise(async () => Server.Default().app.request(SyncPaths.history, { method: "POST", - headers: { "x-opencode-directory": tmp.path, "content-type": "application/json" }, + headers: { "x-opencode-directory": test.directory, "content-type": "application/json" }, body: JSON.stringify({ aggregate: huge }), }), ) @@ -139,15 +128,16 @@ describe("schema-rejection wire shape", () => { const parsed = JSON.parse(body) expect(parsed.data.message).not.toContain(huge) }), - ), + { git: true, config: { formatter: false, lsp: false } }, ) - it.live( + it.instance( "response-encode failure: corrupted stored row returns NamedError-shaped JSON with field path", - withTmp({ config: { formatter: false, lsp: false } }, (tmp) => + () => Effect.gen(function* () { - const sessionID = yield* Effect.promise(() => seedCorruptStepFinishPart(tmp.path)) - const url = `${SessionPaths.messages.replace(":sessionID", sessionID)}?limit=80&directory=${encodeURIComponent(tmp.path)}` + const test = yield* TestInstance + const sessionID = yield* seedCorruptStepFinishPart + const url = `${SessionPaths.messages.replace(":sessionID", sessionID)}?limit=80&directory=${encodeURIComponent(test.directory)}` const res = yield* Effect.promise(async () => Server.Default().app.request(url)) const body = yield* Effect.promise(async () => res.text()) expect(res.status).toBe(400) @@ -157,6 +147,6 @@ describe("schema-rejection wire shape", () => { // Field path in data.message — what made this PR worth shipping. expect(parsed.data.message).toMatch(/output/) }), - ), + { config: { formatter: false, lsp: false } }, ) }) From ded4da735b03edb4023597acf0868b032a3c1185 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:46:23 -0400 Subject: [PATCH 154/378] test: migrate webfetch tool tests to effect runner (#27171) --- packages/opencode/test/tool/webfetch.test.ts | 112 +++++++++---------- 1 file changed, 51 insertions(+), 61 deletions(-) diff --git a/packages/opencode/test/tool/webfetch.test.ts b/packages/opencode/test/tool/webfetch.test.ts index f3890c016..804c6bde2 100644 --- a/packages/opencode/test/tool/webfetch.test.ts +++ b/packages/opencode/test/tool/webfetch.test.ts @@ -1,15 +1,14 @@ -import { describe, expect, test } from "bun:test" -import path from "path" +import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { FetchHttpClient } from "effect/unstable/http" import { Agent } from "../../src/agent/agent" import { Truncate } from "@/tool/truncate" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { WebFetchTool } from "../../src/tool/webfetch" import { SessionID, MessageID } from "../../src/session/schema" +import { Tool } from "@/tool/tool" +import { testEffect } from "../lib/effect" -const projectRoot = path.join(import.meta.dir, "../..") +const it = testEffect(Layer.mergeAll(FetchHttpClient.layer, Truncate.defaultLayer, Agent.defaultLayer)) const ctx = { sessionID: SessionID.make("ses_test"), @@ -22,30 +21,31 @@ const ctx = { ask: () => Effect.void, } -async function withFetch(fetch: (req: Request) => Response | Promise, fn: (url: URL) => Promise) { - using server = Bun.serve({ port: 0, fetch }) - await fn(server.url) -} - -function exec(args: { url: string; format: "text" | "markdown" | "html" }) { - return WebFetchTool.pipe( - Effect.flatMap((info) => info.init()), - Effect.flatMap((tool) => tool.execute(args, ctx)), - Effect.provide(Layer.mergeAll(FetchHttpClient.layer, Truncate.defaultLayer, Agent.defaultLayer)), - Effect.runPromise, +const withFetch = ( + fetch: (req: Request) => Response | Promise, + fn: (url: URL) => Effect.Effect, +) => + Effect.acquireUseRelease( + Effect.sync(() => Bun.serve({ port: 0, fetch })), + (server) => fn(server.url), + (server) => Effect.sync(() => server.stop(true)), ) -} + +const exec = Effect.fn("WebFetchToolTest.exec")(function* (args: Tool.InferParameters) { + const info = yield* WebFetchTool + const tool = yield* info.init() + return yield* tool.execute(args, ctx) +}) describe("tool.webfetch", () => { - test("returns image responses as file attachments", async () => { - const bytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]) - await withFetch( - () => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }), - async (url) => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const result = await exec({ url: new URL("/image.png", url).toString(), format: "markdown" }) + it.instance("returns image responses as file attachments", () => + Effect.gen(function* () { + const bytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]) + yield* withFetch( + () => new Response(bytes, { status: 200, headers: { "content-type": "IMAGE/PNG; charset=binary" } }), + (url) => + Effect.gen(function* () { + const result = yield* exec({ url: new URL("/image.png", url).toString(), format: "markdown" }) expect(result.output).toBe("Image fetched successfully") expect(result.attachments).toBeDefined() expect(result.attachments?.length).toBe(1) @@ -55,50 +55,40 @@ describe("tool.webfetch", () => { expect(result.attachments?.[0]).not.toHaveProperty("id") expect(result.attachments?.[0]).not.toHaveProperty("sessionID") expect(result.attachments?.[0]).not.toHaveProperty("messageID") - }, - }) - }, - ) - }) + }), + ) + }), + ) - test("keeps svg as text output", async () => { - const svg = 'hello' - await withFetch( + it.instance("keeps svg as text output", () => + withFetch( () => - new Response(svg, { + new Response('hello', { status: 200, headers: { "content-type": "image/svg+xml; charset=UTF-8" }, }), - async (url) => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const result = await exec({ url: new URL("/image.svg", url).toString(), format: "html" }) - expect(result.output).toContain(" + Effect.gen(function* () { + const result = yield* exec({ url: new URL("/image.svg", url).toString(), format: "html" }) + expect(result.output).toContain(" { - await withFetch( + it.instance("keeps text responses as text output", () => + withFetch( () => new Response("hello from webfetch", { status: 200, headers: { "content-type": "text/plain; charset=utf-8" }, }), - async (url) => { - await WithInstance.provide({ - directory: projectRoot, - fn: async () => { - const result = await exec({ url: new URL("/file.txt", url).toString(), format: "text" }) - expect(result.output).toBe("hello from webfetch") - expect(result.attachments).toBeUndefined() - }, - }) - }, - ) - }) + (url) => + Effect.gen(function* () { + const result = yield* exec({ url: new URL("/file.txt", url).toString(), format: "text" }) + expect(result.output).toBe("hello from webfetch") + expect(result.attachments).toBeUndefined() + }), + ), + ) }) From b0674b473f06877e6f3e00128c133b5c3f334fd9 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:46:51 -0400 Subject: [PATCH 155/378] test: migrate session action route to effect runner (#27174) --- .../test/server/session-actions.test.ts | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/packages/opencode/test/server/session-actions.test.ts b/packages/opencode/test/server/session-actions.test.ts index 1ccc9bc8e..3891e67f5 100644 --- a/packages/opencode/test/server/session-actions.test.ts +++ b/packages/opencode/test/server/session-actions.test.ts @@ -1,28 +1,14 @@ -import { afterEach, describe, expect, mock, test } from "bun:test" +import { afterEach, describe, expect, mock } from "bun:test" import { Effect } from "effect" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { Server } from "../../src/server/server" import { Session as SessionNs } from "@/session/session" -import type { SessionID } from "../../src/session/schema" import * as Log from "@opencode-ai/core/util/log" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" void Log.init({ print: false }) -function run(fx: Effect.Effect) { - return Effect.runPromise(fx.pipe(Effect.provide(SessionNs.defaultLayer))) -} - -const svc = { - ...SessionNs, - create(input?: SessionNs.CreateInput) { - return run(SessionNs.Service.use((svc) => svc.create(input))) - }, - remove(id: SessionID) { - return run(SessionNs.Service.use((svc) => svc.remove(id))) - }, -} +const it = testEffect(SessionNs.defaultLayer) afterEach(async () => { mock.restore() @@ -30,21 +16,26 @@ afterEach(async () => { }) describe("session action routes", () => { - test("abort route returns success", async () => { - await using tmp = await tmpdir({ git: true }) - await WithInstance.provide({ - directory: tmp.path, - fn: async () => { - const session = await svc.create({}) - const app = Server.Default().app - - const res = await app.request(`/session/${session.id}/abort`, { method: "POST" }) - - expect(res.status).toBe(200) - expect(await res.json()).toBe(true) - - await svc.remove(session.id) - }, - }) - }) + it.instance("abort route returns success", () => + Effect.gen(function* () { + const test = yield* TestInstance + const session = yield* Effect.acquireRelease( + SessionNs.Service.use((svc) => svc.create({})), + (created) => SessionNs.Service.use((svc) => svc.remove(created.id)).pipe(Effect.ignore), + ) + + const res = yield* Effect.promise(() => + Promise.resolve( + Server.Default().app.request(`/session/${session.id}/abort`, { + method: "POST", + headers: { "x-opencode-directory": test.directory }, + }), + ), + ) + + expect(res.status).toBe(200) + expect(yield* Effect.promise(() => res.json())).toBe(true) + }), + { git: true }, + ) }) From 9c54255aebef9842acd82d90d69d8299ac549ab0 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:47:14 -0400 Subject: [PATCH 156/378] fix: migrate sync http api test to effect runner (#27175) --- .../opencode/test/server/httpapi-sync.test.ts | 237 ++++++++++-------- 1 file changed, 130 insertions(+), 107 deletions(-) diff --git a/packages/opencode/test/server/httpapi-sync.test.ts b/packages/opencode/test/server/httpapi-sync.test.ts index cd626c28f..0b5934553 100644 --- a/packages/opencode/test/server/httpapi-sync.test.ts +++ b/packages/opencode/test/server/httpapi-sync.test.ts @@ -1,29 +1,25 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" +import { afterEach, describe, expect, mock, spyOn } from "bun:test" import { Context, Effect } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" import { Server } from "../../src/server/server" import { SyncPaths } from "../../src/server/routes/instance/httpapi/groups/sync" import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { Session } from "@/session/session" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" -import { disposeAllInstances, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" void Log.init({ print: false }) const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES const context = Context.empty() as Context.Context +const it = testEffect(Session.defaultLayer) function app() { return Server.Default().app } -function runSession(fx: Effect.Effect) { - return Effect.runPromise(fx.pipe(Effect.provide(Session.defaultLayer))) -} - afterEach(async () => { mock.restore() Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces @@ -32,111 +28,138 @@ afterEach(async () => { }) describe("sync HttpApi", () => { - test("serves sync routes", async () => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true - await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) - const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } - const info = spyOn(Log.create({ service: "server.sync" }), "info") - - const session = await WithInstance.provide({ - directory: tmp.path, - fn: async () => runSession(Session.Service.use((svc) => svc.create({ title: "sync" }))), - }) + it.instance( + "serves sync routes", + () => + Effect.gen(function* () { + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true + const tmp = yield* TestInstance + const headers = { "x-opencode-directory": tmp.directory, "content-type": "application/json" } + const info = spyOn(Log.create({ service: "server.sync" }), "info") + const session = yield* Session.Service.use((svc) => svc.create({ title: "sync" })) - const started = await app().request(SyncPaths.start, { method: "POST", headers }) - expect(started.status).toBe(200) - expect(await started.json()).toBe(true) + const started = yield* Effect.promise(() => + Promise.resolve(app().request(SyncPaths.start, { method: "POST", headers })), + ) + expect(started.status).toBe(200) + expect(yield* Effect.promise(() => started.json())).toBe(true) - const history = await app().request(SyncPaths.history, { - method: "POST", - headers, - body: JSON.stringify({}), - }) - expect(history.status).toBe(200) - const rows = (await history.json()) as Array<{ - id: string - aggregate_id: string - seq: number - type: string - data: Record - }> - expect(rows.map((row) => row.aggregate_id)).toContain(session.id) + const history = yield* Effect.promise(() => + Promise.resolve( + app().request(SyncPaths.history, { + method: "POST", + headers, + body: JSON.stringify({}), + }), + ), + ) + expect(history.status).toBe(200) + const rows = (yield* Effect.promise(() => history.json())) as Array<{ + id: string + aggregate_id: string + seq: number + type: string + data: Record + }> + expect(rows.map((row) => row.aggregate_id)).toContain(session.id) - const replayed = await app().request(SyncPaths.replay, { - method: "POST", - headers, - body: JSON.stringify({ - directory: tmp.path, - events: rows - .filter((row) => row.aggregate_id === session.id) - .map((row) => ({ - id: row.id, - aggregateID: row.aggregate_id, - seq: row.seq, - type: row.type, - data: row.data, - })), + const replayed = yield* Effect.promise(() => + Promise.resolve( + app().request(SyncPaths.replay, { + method: "POST", + headers, + body: JSON.stringify({ + directory: tmp.directory, + events: rows + .filter((row) => row.aggregate_id === session.id) + .map((row) => ({ + id: row.id, + aggregateID: row.aggregate_id, + seq: row.seq, + type: row.type, + data: row.data, + })), + }), + }), + ), + ) + expect(replayed.status).toBe(200) + expect(yield* Effect.promise(() => replayed.json())).toEqual({ sessionID: session.id }) + expect(info.mock.calls.some(([message]) => message === "sync replay requested")).toBe(true) + expect(info.mock.calls.some(([message]) => message === "sync replay complete")).toBe(true) }), - }) - expect(replayed.status).toBe(200) - expect(await replayed.json()).toEqual({ sessionID: session.id }) - expect(info.mock.calls.some(([message]) => message === "sync replay requested")).toBe(true) - expect(info.mock.calls.some(([message]) => message === "sync replay complete")).toBe(true) - }) + { git: true, config: { formatter: false, lsp: false } }, + ) - test("validates seq values", async () => { - await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) - const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } - const cases = [ - { - path: SyncPaths.history, - body: { aggregate: -1 }, - }, - { - path: SyncPaths.history, - body: { aggregate: 1.5 }, - }, - { - path: SyncPaths.replay, - body: { - directory: tmp.path, - events: [{ id: "event", aggregateID: "session", seq: -1, type: "session.created", data: {} }], - }, - }, - { - path: SyncPaths.replay, - body: { - directory: tmp.path, - events: [{ id: "event", aggregateID: "session", seq: 1.5, type: "session.created", data: {} }], - }, - }, - ] + it.instance( + "validates seq values", + () => + Effect.gen(function* () { + const tmp = yield* TestInstance + const headers = { "x-opencode-directory": tmp.directory, "content-type": "application/json" } + const cases = [ + { + path: SyncPaths.history, + body: { aggregate: -1 }, + }, + { + path: SyncPaths.history, + body: { aggregate: 1.5 }, + }, + { + path: SyncPaths.replay, + body: { + directory: tmp.directory, + events: [{ id: "event", aggregateID: "session", seq: -1, type: "session.created", data: {} }], + }, + }, + { + path: SyncPaths.replay, + body: { + directory: tmp.directory, + events: [{ id: "event", aggregateID: "session", seq: 1.5, type: "session.created", data: {} }], + }, + }, + ] - for (const item of cases) { - const response = await app().request(item.path, { - method: "POST", - headers, - body: JSON.stringify(item.body), - }) - expect(response.status).toBe(400) - } - }) - - test.todo("returns structured validation errors", async () => { - await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) - const response = await ExperimentalHttpApiServer.webHandler().handler( - new Request(`http://localhost${SyncPaths.history}`, { - method: "POST", - headers: { "x-opencode-directory": tmp.path, "content-type": "application/json" }, - body: JSON.stringify({ aggregate: -1 }), + for (const item of cases) { + const response = yield* Effect.promise(() => + Promise.resolve( + app().request(item.path, { + method: "POST", + headers, + body: JSON.stringify(item.body), + }), + ), + ) + expect(response.status).toBe(400) + } }), - context, - ) + { git: true, config: { formatter: false, lsp: false } }, + ) + + it.instance.skip( + "returns structured validation errors", + () => + Effect.gen(function* () { + const tmp = yield* TestInstance + const response = yield* Effect.promise(() => + ExperimentalHttpApiServer.webHandler().handler( + new Request(`http://localhost${SyncPaths.history}`, { + method: "POST", + headers: { "x-opencode-directory": tmp.directory, "content-type": "application/json" }, + body: JSON.stringify({ aggregate: -1 }), + }), + context, + ), + ) - expect(response.status).toBe(400) - expect(response.headers.get("content-type") ?? "").toContain("application/json") - const body = (await response.json()) as Record - expect(body.success).toBe(false) - expect(Array.isArray(body.error) || Array.isArray(body.errors)).toBe(true) - }) + expect(response.status).toBe(400) + expect(response.headers.get("content-type") ?? "").toContain("application/json") + const body = (yield* Effect.promise(() => response.json())) as Record + expect(body.success).toBe(false) + expect(Array.isArray(body.error) || Array.isArray(body.errors)).toBe(true) + }), + { git: true, config: { formatter: false, lsp: false } }, + ) }) From 3301fad8cdfac520ad8e8238df2c4433d4cf24ef Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:47:49 -0400 Subject: [PATCH 157/378] test: migrate app runtime logger effect tests (#27176) --- .../test/effect/app-runtime-logger.test.ts | 93 ++++++++++--------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/packages/opencode/test/effect/app-runtime-logger.test.ts b/packages/opencode/test/effect/app-runtime-logger.test.ts index fe9516ef9..e4ce6511d 100644 --- a/packages/opencode/test/effect/app-runtime-logger.test.ts +++ b/packages/opencode/test/effect/app-runtime-logger.test.ts @@ -1,12 +1,13 @@ import { expect } from "bun:test" -import { Context, Effect, Layer, Logger } from "effect" +import { Context, Deferred, Effect, Fiber, Layer, Logger } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { AppRuntime } from "../../src/effect/app-runtime" +import { AppLayer } from "../../src/effect/app-runtime" import { EffectBridge } from "@/effect/bridge" import { InstanceRef } from "../../src/effect/instance-ref" import * as EffectLogger from "@opencode-ai/core/effect/logger" -import { makeRuntime } from "../../src/effect/run-service" -import { provideInstance, tmpdirScoped } from "../fixture/fixture" +import * as Observability from "@opencode-ai/core/effect/observability" +import { attach } from "../../src/effect/run-service" +import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" const it = testEffect(CrossSpawnSpawner.defaultLayer) @@ -35,64 +36,70 @@ it.live("makeRuntime installs EffectLogger through Observability.layer", () => }), ) - const current = yield* Effect.promise(() => makeRuntime(Dummy, layer).runPromise((svc) => svc.current())) + const current = yield* Dummy.use((svc) => svc.current()).pipe( + Effect.provide(Layer.provideMerge(layer, Observability.layer)), + ) expect(current.effectLogger).toBe(true) expect(current.defaultLogger).toBe(false) }), ) -it.live("AppRuntime also installs EffectLogger through Observability.layer", () => +it.live("AppLayer also installs EffectLogger through Observability.layer", () => Effect.gen(function* () { - const current = yield* Effect.promise(() => - AppRuntime.runPromise(Effect.map(Effect.service(Logger.CurrentLoggers), check)), - ) + const current = yield* Effect.map(Effect.service(Logger.CurrentLoggers), check).pipe(Effect.provide(AppLayer)) expect(current.effectLogger).toBe(true) expect(current.defaultLogger).toBe(false) }), ) -it.live("AppRuntime attaches InstanceRef from ALS", () => - Effect.gen(function* () { - const dir = yield* tmpdirScoped({ git: true }) - const current = yield* Effect.promise(() => - AppRuntime.runPromise( +it.instance( + "attach preserves InstanceRef from the current fiber context", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const current = yield* attach( Effect.gen(function* () { return (yield* InstanceRef)?.directory }), - ), - ).pipe(provideInstance(dir)) + ) - expect(current).toBe(dir) - }), + expect(current).toBe(test.directory) + }), + { git: true }, ) -it.live("EffectBridge preserves logger and instance context across async boundaries", () => - Effect.gen(function* () { - const dir = yield* tmpdirScoped({ git: true }) - const result = yield* Effect.promise(() => - AppRuntime.runPromise( - Effect.gen(function* () { - const bridge = yield* EffectBridge.make() - return yield* Effect.promise(() => - Promise.resolve().then(() => - bridge.promise( - Effect.gen(function* () { - return { - directory: (yield* InstanceRef)?.directory, - ...check(yield* Effect.service(Logger.CurrentLoggers)), - } - }), - ), +it.instance( + "EffectBridge preserves logger and instance context across async boundaries", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const bridge = yield* EffectBridge.make() + const started = yield* Deferred.make() + + const fiber = yield* Effect.gen(function* () { + yield* Deferred.succeed(started, undefined) + return yield* Effect.promise(() => + Promise.resolve().then(() => + bridge.promise( + Effect.gen(function* () { + return { + directory: (yield* InstanceRef)?.directory, + ...check(yield* Effect.service(Logger.CurrentLoggers)), + } + }), ), - ) - }), - ), - ).pipe(provideInstance(dir)) + ), + ) + }).pipe(Effect.forkScoped) - expect(result.directory).toBe(dir) - expect(result.effectLogger).toBe(true) - expect(result.defaultLogger).toBe(false) - }), + yield* Deferred.await(started) + const result = yield* Fiber.join(fiber) + + expect(result.directory).toBe(test.directory) + expect(result.effectLogger).toBe(true) + expect(result.defaultLogger).toBe(false) + }).pipe(Effect.provide(Observability.layer)), + { git: true }, ) From 72ce24c2002f8e6139c3edb26ee8b6534799c6bb Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:48:17 -0400 Subject: [PATCH 158/378] test: migrate effect cmd ALS test (#27173) --- .../test/cli/effect-cmd-instance-als.test.ts | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/packages/opencode/test/cli/effect-cmd-instance-als.test.ts b/packages/opencode/test/cli/effect-cmd-instance-als.test.ts index de6fed8da..122b87f17 100644 --- a/packages/opencode/test/cli/effect-cmd-instance-als.test.ts +++ b/packages/opencode/test/cli/effect-cmd-instance-als.test.ts @@ -1,8 +1,13 @@ -import { afterEach, expect, test } from "bun:test" +import { afterEach, expect } from "bun:test" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Effect } from "effect" -import fs from "fs/promises" +import { fileURLToPath } from "url" +import { InstanceRef } from "../../src/effect/instance-ref" import { Instance } from "../../src/project/instance" -import { disposeAllInstances, provideTestInstance, tmpdir } from "../fixture/fixture" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" + +const it = testEffect(AppFileSystem.defaultLayer) afterEach(async () => { await disposeAllInstances() @@ -14,35 +19,40 @@ afterEach(async () => { // has lost the outer InstanceRef. Services that read `InstanceState.context` // then fall back to `Instance.current` ALS, which must be installed at the JS // callback boundary (Node ALS persists across awaits, Effect's fiber context -// does not). `provideTestInstance` mirrors effectCmd's load + ALS-restore wrap. +// does not). `it.instance` provides the loaded InstanceRef; the explicit +// Instance.restore mirrors effectCmd's load + ALS-restore wrap. // Pins effect-cmd.ts directly: the pattern test below exercises the load + -// Instance.restore + dispose triple via the shared `provideTestInstance` fixture, +// Instance.restore boundary via the shared `it.instance` fixture, // so a regression that removed `Instance.restore` from effect-cmd.ts wouldn't // fail it. This grep guards the actual production callsite. -test("effect-cmd.ts wraps the handler body in Instance.restore", async () => { - const source = await fs.readFile(new URL("../../src/cli/effect-cmd.ts", import.meta.url), "utf8") - expect(source).toContain("Instance.restore(ctx") -}) +it.live("effect-cmd.ts wraps the handler body in Instance.restore", () => + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const source = yield* fs.readFileString(fileURLToPath(new URL("../../src/cli/effect-cmd.ts", import.meta.url))) + expect(source).toContain("Instance.restore(ctx") + }), +) + +it.instance( + "Instance.current reachable after await inside restored Effect.promise(async)", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const ctx = yield* InstanceRef + if (!ctx) throw new Error("InstanceRef not provided") -test("Instance.current reachable from inner runPromise inside Effect.promise(async)", async () => { - await using dir = await tmpdir({ git: true }) - await provideTestInstance({ - directory: dir.path, - fn: () => - Effect.runPromise( - Effect.promise(async () => { - await new Promise((r) => setTimeout(r, 5)) - const current = await Effect.runPromise( - Effect.sync(() => { - try { - return Instance.current - } catch { - return undefined - } - }), - ) - expect(current?.directory).toBe(dir.path) + const current = yield* Effect.promise(() => + Instance.restore(ctx, async () => { + await Promise.resolve() + try { + return Instance.current + } catch { + return undefined + } }), - ), - }) -}) + ) + + expect(current?.directory).toBe(test.directory) + }), + { git: true }, +) From 937a3c10b3ecaaaa7543cbd9d98fe69e59482743 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 12 May 2026 23:49:28 +0000 Subject: [PATCH 159/378] chore: generate --- .../test/server/session-actions.test.ts | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/opencode/test/server/session-actions.test.ts b/packages/opencode/test/server/session-actions.test.ts index 3891e67f5..44e324b71 100644 --- a/packages/opencode/test/server/session-actions.test.ts +++ b/packages/opencode/test/server/session-actions.test.ts @@ -16,26 +16,28 @@ afterEach(async () => { }) describe("session action routes", () => { - it.instance("abort route returns success", () => - Effect.gen(function* () { - const test = yield* TestInstance - const session = yield* Effect.acquireRelease( - SessionNs.Service.use((svc) => svc.create({})), - (created) => SessionNs.Service.use((svc) => svc.remove(created.id)).pipe(Effect.ignore), - ) + it.instance( + "abort route returns success", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const session = yield* Effect.acquireRelease( + SessionNs.Service.use((svc) => svc.create({})), + (created) => SessionNs.Service.use((svc) => svc.remove(created.id)).pipe(Effect.ignore), + ) - const res = yield* Effect.promise(() => - Promise.resolve( - Server.Default().app.request(`/session/${session.id}/abort`, { - method: "POST", - headers: { "x-opencode-directory": test.directory }, - }), - ), - ) + const res = yield* Effect.promise(() => + Promise.resolve( + Server.Default().app.request(`/session/${session.id}/abort`, { + method: "POST", + headers: { "x-opencode-directory": test.directory }, + }), + ), + ) - expect(res.status).toBe(200) - expect(yield* Effect.promise(() => res.json())).toBe(true) - }), + expect(res.status).toBe(200) + expect(yield* Effect.promise(() => res.json())).toBe(true) + }), { git: true }, ) }) From 1b6599f411cb67601cc48fc8e348056dcaf2ec3f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:53:46 -0400 Subject: [PATCH 160/378] test(plugin): use noop dependency boundaries (#27148) --- packages/opencode/test/plugin/trigger.test.ts | 47 ++++++++++------ .../test/plugin/workspace-adapter.test.ts | 53 ++++++++++++------- 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/packages/opencode/test/plugin/trigger.test.ts b/packages/opencode/test/plugin/trigger.test.ts index 5e16af42b..18fe0e82e 100644 --- a/packages/opencode/test/plugin/trigger.test.ts +++ b/packages/opencode/test/plugin/trigger.test.ts @@ -1,26 +1,43 @@ -import { afterAll, describe, expect } from "bun:test" -import { Effect, Layer } from "effect" +import { describe, expect } from "bun:test" +import { Effect, Layer, Option } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import path from "path" import { pathToFileURL } from "url" +import { Account } from "../../src/account/account" +import { Auth } from "../../src/auth" +import { Bus } from "../../src/bus" +import { Config } from "../../src/config/config" +import { Env } from "../../src/env" +import { Plugin } from "../../src/plugin/index" import { ModelID, ProviderID } from "../../src/provider/schema" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" +import { NpmTest } from "../fake/npm" -const disableDefault = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS -process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" - -const { Plugin } = await import("../../src/plugin/index") -const it = testEffect(Layer.mergeAll(Plugin.defaultLayer, CrossSpawnSpawner.defaultLayer)) -const systemHook = "experimental.chat.system.transform" - -afterAll(() => { - if (disableDefault === undefined) { - delete process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS - return - } - process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = disableDefault +const emptyAccount = Layer.mock(Account.Service)({ + active: () => Effect.succeed(Option.none()), + activeOrg: () => Effect.succeed(Option.none()), }) +const emptyAuth = Layer.mock(Auth.Service)({ + all: () => Effect.succeed({}), +}) +const configLayer = Config.layer.pipe( + Layer.provide(EffectFlock.defaultLayer), + Layer.provide(AppFileSystem.defaultLayer), + Layer.provide(Env.defaultLayer), + Layer.provide(emptyAuth), + Layer.provide(emptyAccount), + Layer.provide(NpmTest.noop), +) +const it = testEffect( + Layer.mergeAll( + Plugin.layer.pipe(Layer.provide(Bus.layer), Layer.provide(configLayer)), + CrossSpawnSpawner.defaultLayer, + ), +) +const systemHook = "experimental.chat.system.transform" function withProject(source: string, self: Effect.Effect) { return provideTmpdirInstance((dir) => diff --git a/packages/opencode/test/plugin/workspace-adapter.test.ts b/packages/opencode/test/plugin/workspace-adapter.test.ts index 9199a85a6..d4aaae4a9 100644 --- a/packages/opencode/test/plugin/workspace-adapter.test.ts +++ b/packages/opencode/test/plugin/workspace-adapter.test.ts @@ -1,25 +1,46 @@ import { afterAll, afterEach, describe, expect } from "bun:test" -import { Effect, Layer } from "effect" +import { Effect, Layer, Option } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" +import { Flag } from "@opencode-ai/core/flag/flag" import path from "path" import { pathToFileURL } from "url" +import { Account } from "../../src/account/account" +import { Auth } from "../../src/auth" +import { Bus } from "../../src/bus" +import { Config } from "../../src/config/config" +import { Env } from "../../src/env" +import { Workspace } from "../../src/control-plane/workspace" +import { Plugin } from "../../src/plugin/index" +import { InstanceBootstrap } from "../../src/project/bootstrap-service" +import { Instance } from "../../src/project/instance" +import { InstanceStore } from "../../src/project/instance-store" import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" +import { NpmTest } from "../fake/npm" -const disableDefault = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS -process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" - -const { Flag } = await import("@opencode-ai/core/flag/flag") -const { Plugin } = await import("../../src/plugin/index") -const { Workspace } = await import("../../src/control-plane/workspace") -const { InstanceBootstrap } = await import("../../src/project/bootstrap") -const { Instance } = await import("../../src/project/instance") -const { InstanceStore } = await import("../../src/project/instance-store") +const emptyAccount = Layer.mock(Account.Service)({ + active: () => Effect.succeed(Option.none()), + activeOrg: () => Effect.succeed(Option.none()), +}) +const emptyAuth = Layer.mock(Auth.Service)({ + all: () => Effect.succeed({}), +}) +const configLayer = Config.layer.pipe( + Layer.provide(EffectFlock.defaultLayer), + Layer.provide(AppFileSystem.defaultLayer), + Layer.provide(Env.defaultLayer), + Layer.provide(emptyAuth), + Layer.provide(emptyAccount), + Layer.provide(NpmTest.noop), +) +const pluginLayer = Plugin.layer.pipe(Layer.provide(Bus.layer), Layer.provide(configLayer)) +const noopBootstrapLayer = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) const workspaceLayer = Workspace.defaultLayer.pipe( - Layer.provide(InstanceStore.defaultLayer), - Layer.provide(InstanceBootstrap.defaultLayer), + Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrapLayer))), ) -const it = testEffect(Layer.mergeAll(Plugin.defaultLayer, workspaceLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect(Layer.mergeAll(pluginLayer, workspaceLayer, CrossSpawnSpawner.defaultLayer)) const experimental = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES @@ -30,12 +51,6 @@ afterEach(async () => { }) afterAll(() => { - if (disableDefault === undefined) { - delete process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS - } else { - process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = disableDefault - } - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = experimental }) From d8c8322ddf88ae3b27335b7c8f1bf5eb2d5fce01 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 19:55:07 -0400 Subject: [PATCH 161/378] test: migrate worktree tests to effect runner (#27177) --- .../opencode/test/project/worktree.test.ts | 474 +++++++++--------- 1 file changed, 232 insertions(+), 242 deletions(-) diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index b1b9d22b7..fe56008b0 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -1,302 +1,292 @@ -import { $ } from "bun" import { afterEach, describe, expect } from "bun:test" -import * as fs from "fs/promises" import path from "path" -import { Cause, Effect, Exit, Layer } from "effect" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { Instance } from "../../src/project/instance" -import { WithInstance } from "../../src/project/with-instance" -import { InstanceRuntime } from "../../src/project/instance-runtime" +import { Cause, Deferred, Effect, Exit, Layer } from "effect" +import { GlobalBus, type GlobalEvent } from "../../src/bus/global" +import { Git } from "../../src/git" import { Worktree } from "../../src/worktree" -import { disposeAllInstances, provideInstance, provideTmpdirInstance } from "../fixture/fixture" +import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Worktree.defaultLayer, CrossSpawnSpawner.defaultLayer)) -const wintest = process.platform !== "win32" ? it.live : it.live.skip +const it = testEffect( + Layer.mergeAll(Worktree.defaultLayer, AppFileSystem.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer), +) +const wintest = process.platform !== "win32" ? it.instance : it.instance.skip function normalize(input: string) { return input.replace(/\\/g, "/").toLowerCase() } -async function waitReady() { - const { GlobalBus } = await import("../../src/bus/global") +const waitReady = Effect.fn("WorktreeTest.waitReady")(function* () { + const ready = yield* Deferred.make<{ name: string; branch?: string }>() + const on = (evt: GlobalEvent) => { + if (evt.payload.type !== Worktree.Event.Ready.type) return + Deferred.doneUnsafe(ready, Effect.succeed(evt.payload.properties)) + } - return await new Promise<{ name: string; branch?: string }>((resolve, reject) => { - const timer = setTimeout(() => { - GlobalBus.off("event", on) - reject(new Error("timed out waiting for worktree.ready")) - }, 10_000) + GlobalBus.on("event", on) + yield* Effect.addFinalizer(() => Effect.sync(() => GlobalBus.off("event", on))) - function on(evt: { directory?: string; payload: { type: string; properties: { name: string; branch?: string } } }) { - if (evt.payload.type !== Worktree.Event.Ready.type) return - clearTimeout(timer) - GlobalBus.off("event", on) - resolve(evt.payload.properties) - } + return Deferred.await(ready).pipe( + Effect.race( + Effect.sleep("10 seconds").pipe( + Effect.flatMap(() => Effect.fail(new Error("timed out waiting for worktree.ready"))), + ), + ), + ) +}) - GlobalBus.on("event", on) - }) -} +const git = Effect.fn("WorktreeTest.git")(function* (cwd: string, args: string[]) { + const service = yield* Git.Service + const result = yield* service.run(args, { cwd }) + if (result.exitCode !== 0) throw new Error(`git ${args.join(" ")} failed: ${result.stderr.toString("utf8")}`) + return result.text() +}) + +const gitResult = Effect.fn("WorktreeTest.gitResult")(function* (cwd: string, args: string[]) { + const service = yield* Git.Service + return yield* service.run(args, { cwd }) +}) describe("Worktree", () => { afterEach(() => disposeAllInstances()) describe("makeWorktreeInfo", () => { - it.live("returns info with name, branch, and directory", () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const info = yield* svc.makeWorktreeInfo() - - expect(info.name).toBeDefined() - expect(typeof info.name).toBe("string") - expect(info.branch).toBe(`opencode/${info.name}`) - expect(info.directory).toContain(info.name) - }), - { git: true }, - ), - ) + it.instance( + "returns info with name, branch, and directory", + () => + Effect.gen(function* () { + const svc = yield* Worktree.Service + const info = yield* svc.makeWorktreeInfo() - it.live("uses provided name as base", () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const info = yield* svc.makeWorktreeInfo({ name: "my-feature" }) - - expect(info.name).toBe("my-feature") - expect(info.branch).toBe("opencode/my-feature") - }), - { git: true }, - ), + expect(info.name).toBeDefined() + expect(typeof info.name).toBe("string") + expect(info.branch).toBe(`opencode/${info.name}`) + expect(info.directory).toContain(info.name) + }), + { git: true }, ) - it.live("slugifies the provided name", () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const info = yield* svc.makeWorktreeInfo({ name: "My Feature Branch!" }) + it.instance( + "uses provided name as base", + () => + Effect.gen(function* () { + const svc = yield* Worktree.Service + const info = yield* svc.makeWorktreeInfo({ name: "my-feature" }) - expect(info.name).toBe("my-feature-branch") - }), - { git: true }, - ), + expect(info.name).toBe("my-feature") + expect(info.branch).toBe("opencode/my-feature") + }), + { git: true }, ) - it.live("omits branch for detached info", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const svc = yield* Worktree.Service - yield* Effect.promise(() => $`git branch opencode/my-feature`.cwd(dir).quiet()) - - const info = yield* svc.makeWorktreeInfo({ name: "my-feature", detached: true }) + it.instance( + "slugifies the provided name", + () => + Effect.gen(function* () { + const svc = yield* Worktree.Service + const info = yield* svc.makeWorktreeInfo({ name: "My Feature Branch!" }) - expect(info.name).toBe("my-feature") - expect(info.branch).toBeUndefined() - }), - { git: true }, - ), + expect(info.name).toBe("my-feature-branch") + }), + { git: true }, ) - it.live("throws NotGitError for non-git directories", () => - provideTmpdirInstance(() => + it.instance( + "omits branch for detached info", + () => Effect.gen(function* () { + const test = yield* TestInstance const svc = yield* Worktree.Service - const exit = yield* Effect.exit(svc.makeWorktreeInfo()) + yield* git(test.directory, ["branch", "opencode/my-feature"]) + + const info = yield* svc.makeWorktreeInfo({ name: "my-feature", detached: true }) - expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Worktree.NotGitError) + expect(info.name).toBe("my-feature") + expect(info.branch).toBeUndefined() }), - ), + { git: true }, ) - wintest("creates detached git worktree when info has no branch", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const info = yield* svc.makeWorktreeInfo({ name: "detached-test", detached: true }) - const ready = waitReady() - yield* svc.createFromInfo(info) - - const list = yield* Effect.promise(() => $`git worktree list --porcelain`.cwd(dir).quiet().text()) - const normalizedList = normalize(list) - const normalizedDir = normalize(info.directory) - expect(normalizedList).toContain(normalizedDir) - - const branch = yield* Effect.promise(() => - $`git symbolic-ref -q --short HEAD`.cwd(info.directory).quiet().nothrow(), - ) - expect(branch.exitCode).not.toBe(0) - - const props = yield* Effect.promise(() => ready) - expect(props.name).toBe(info.name) - expect(props.branch).toBeUndefined() - - yield* svc.remove({ directory: info.directory }) - }), - { git: true }, - ), + it.instance("throws NotGitError for non-git directories", () => + Effect.gen(function* () { + const svc = yield* Worktree.Service + const exit = yield* Effect.exit(svc.makeWorktreeInfo()) + + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Worktree.NotGitError) + }), + ) + + wintest( + "creates detached git worktree when info has no branch", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const svc = yield* Worktree.Service + const info = yield* svc.makeWorktreeInfo({ name: "detached-test", detached: true }) + const ready = yield* waitReady() + yield* svc.createFromInfo(info) + + const list = yield* git(test.directory, ["worktree", "list", "--porcelain"]) + const normalizedList = normalize(list) + const normalizedDir = normalize(info.directory) + expect(normalizedList).toContain(normalizedDir) + + const branch = yield* gitResult(info.directory, ["symbolic-ref", "-q", "--short", "HEAD"]) + expect(branch.exitCode).not.toBe(0) + + const props = yield* ready + expect(props.name).toBe(info.name) + expect(props.branch).toBeUndefined() + + yield* svc.remove({ directory: info.directory }) + }), + { git: true }, ) }) describe("create + remove lifecycle", () => { - it.live("create returns worktree info and remove cleans up", () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const info = yield* svc.create() - - expect(info.name).toBeDefined() - expect(info.branch ?? "").toStartWith("opencode/") - expect(info.directory).toBeDefined() - - yield* Effect.promise(() => Bun.sleep(1000)) - - const ok = yield* svc.remove({ directory: info.directory }) - expect(ok).toBe(true) - }), - { git: true }, - ), + it.instance( + "create returns worktree info and remove cleans up", + () => + Effect.gen(function* () { + const svc = yield* Worktree.Service + const ready = yield* waitReady() + const info = yield* svc.create() + + expect(info.name).toBeDefined() + expect(info.branch ?? "").toStartWith("opencode/") + expect(info.directory).toBeDefined() + + yield* ready + + const ok = yield* svc.remove({ directory: info.directory }) + expect(ok).toBe(true) + }), + { git: true }, ) - it.live("create returns after setup and fires Event.Ready after bootstrap", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const ready = waitReady() - const info = yield* svc.create() - - expect(info.name).toBeDefined() - expect(info.branch ?? "").toStartWith("opencode/") - - const text = yield* Effect.promise(() => $`git worktree list --porcelain`.cwd(dir).quiet().text()) - const next = yield* Effect.promise(() => fs.realpath(info.directory).catch(() => info.directory)) - expect(normalize(text)).toContain(normalize(next)) - - const props = yield* Effect.promise(() => ready) - expect(props.name).toBe(info.name) - expect(props.branch).toBe(info.branch) - - yield* Effect.promise(() => - WithInstance.provide({ - directory: info.directory, - fn: () => InstanceRuntime.disposeInstance(Instance.current), - }), - ) - yield* Effect.promise(() => Bun.sleep(100)) - yield* svc.remove({ directory: info.directory }) - }), - { git: true }, - ), + it.instance( + "create returns after setup and fires Event.Ready after bootstrap", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const fs = yield* AppFileSystem.Service + const svc = yield* Worktree.Service + const ready = yield* waitReady() + const info = yield* svc.create() + + expect(info.name).toBeDefined() + expect(info.branch ?? "").toStartWith("opencode/") + + const text = yield* git(test.directory, ["worktree", "list", "--porcelain"]) + const next = yield* fs.realPath(info.directory).pipe(Effect.catch(() => Effect.succeed(info.directory))) + expect(normalize(text)).toContain(normalize(next)) + + const props = yield* ready + expect(props.name).toBe(info.name) + expect(props.branch).toBe(info.branch) + + yield* svc.remove({ directory: info.directory }) + }), + { git: true }, ) - it.live("create with custom name", () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const ready = waitReady() - const info = yield* svc.create({ name: "test-workspace" }) - - expect(info.name).toBe("test-workspace") - expect(info.branch).toBe("opencode/test-workspace") - - yield* Effect.promise(() => ready) - yield* Effect.promise(() => - WithInstance.provide({ - directory: info.directory, - fn: () => InstanceRuntime.disposeInstance(Instance.current), - }), - ) - yield* Effect.promise(() => Bun.sleep(100)) - yield* svc.remove({ directory: info.directory }) - }), - { git: true }, - ), + it.instance( + "create with custom name", + () => + Effect.gen(function* () { + const svc = yield* Worktree.Service + const ready = yield* waitReady() + const info = yield* svc.create({ name: "test-workspace" }) + + expect(info.name).toBe("test-workspace") + expect(info.branch).toBe("opencode/test-workspace") + + yield* ready + yield* svc.remove({ directory: info.directory }) + }), + { git: true }, ) }) describe("createFromInfo", () => { - wintest("creates git worktree and boots asynchronously", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const info = yield* svc.makeWorktreeInfo({ name: "from-info-test" }) - const ready = waitReady() - yield* svc.createFromInfo(info) - - const list = yield* Effect.promise(() => $`git worktree list --porcelain`.cwd(dir).quiet().text()) - const normalizedList = list.replace(/\\/g, "/") - const normalizedDir = info.directory.replace(/\\/g, "/") - expect(normalizedList).toContain(normalizedDir) - - yield* Effect.promise(() => ready) - yield* svc.remove({ directory: info.directory }) - }), - { git: true }, - ), + wintest( + "creates git worktree and boots asynchronously", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const svc = yield* Worktree.Service + const info = yield* svc.makeWorktreeInfo({ name: "from-info-test" }) + const ready = yield* waitReady() + yield* svc.createFromInfo(info) + + const list = yield* git(test.directory, ["worktree", "list", "--porcelain"]) + const normalizedList = list.replace(/\\/g, "/") + const normalizedDir = info.directory.replace(/\\/g, "/") + expect(normalizedList).toContain(normalizedDir) + + yield* ready + yield* svc.remove({ directory: info.directory }) + }), + { git: true }, ) }) describe("list", () => { - it.live("uses parent folder name when worktree basename matches the primary worktree", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const parent = path.join(path.dirname(dir), `${path.basename(dir)}-parent`) - const target = path.join(parent, path.basename(dir)) - const branch = `same-basename-list-${Date.now()}` - - yield* Effect.promise(() => fs.mkdir(parent, { recursive: true })) - yield* Effect.promise(() => $`git worktree add -b ${branch} ${target}`.cwd(dir).quiet()) - - const list = yield* svc.list() - const directory = yield* Effect.promise(() => fs.realpath(target).catch(() => target)) - - expect(list.map((item) => ({ ...item, directory: normalize(item.directory) }))).toContainEqual({ - name: path.basename(parent), - branch, - directory: normalize(directory), - }) - - yield* svc.remove({ directory: target }) - }), - { git: true }, - ), + it.instance( + "uses parent folder name when worktree basename matches the primary worktree", + () => + Effect.gen(function* () { + const test = yield* TestInstance + const fs = yield* AppFileSystem.Service + const svc = yield* Worktree.Service + const parent = path.join(path.dirname(test.directory), `${path.basename(test.directory)}-parent`) + const target = path.join(parent, path.basename(test.directory)) + const branch = `same-basename-list-${Date.now()}` + + yield* fs.ensureDir(parent) + yield* git(test.directory, ["worktree", "add", "-b", branch, target]) + + const list = yield* svc.list() + const directory = yield* fs.realPath(target).pipe(Effect.catch(() => Effect.succeed(target))) + + expect(list.map((item) => ({ ...item, directory: normalize(item.directory) }))).toContainEqual({ + name: path.basename(parent), + branch, + directory: normalize(directory), + }) + + yield* svc.remove({ directory: target }) + }), + { git: true }, ) }) describe("remove edge cases", () => { - it.live("remove non-existent directory succeeds silently", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const ok = yield* svc.remove({ directory: path.join(dir, "does-not-exist") }) - expect(ok).toBe(true) - }), - { git: true }, - ), - ) - - it.live("throws NotGitError for non-git directories", () => - provideTmpdirInstance(() => + it.instance( + "remove non-existent directory succeeds silently", + () => Effect.gen(function* () { + const test = yield* TestInstance const svc = yield* Worktree.Service - const exit = yield* Effect.exit(svc.remove({ directory: "/tmp/fake" })) - - expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Worktree.NotGitError) + const ok = yield* svc.remove({ directory: path.join(test.directory, "does-not-exist") }) + expect(ok).toBe(true) }), - ), + { git: true }, + ) + + it.instance("throws NotGitError for non-git directories", () => + Effect.gen(function* () { + const test = yield* TestInstance + const svc = yield* Worktree.Service + const exit = yield* Effect.exit(svc.remove({ directory: path.join(test.directory, "fake") })) + + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(Worktree.NotGitError) + }), ) }) }) From 1d243ce25a546986d47322e473212ea69da9c6a7 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Tue, 12 May 2026 20:06:29 -0400 Subject: [PATCH 162/378] harden cache usage --- .github/actions/setup-bun/action.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 35f42462b..5b44517ec 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -33,8 +33,9 @@ runs: shell: bash run: echo "dir=$(bun pm cache)" >> "$GITHUB_OUTPUT" - - name: Cache Bun dependencies - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - name: Restore Bun dependencies + id: bun-cache + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ steps.cache.outputs.dir }} key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} @@ -56,3 +57,10 @@ runs: bun install ${{ inputs.install-flags }} fi shell: bash + + - name: Save Bun dependencies + if: steps.bun-cache.outputs.cache-hit != 'true' && github.event_name != 'pull_request' && github.event_name != 'pull_request_target' + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ steps.cache.outputs.dir }} + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} From d0844c600be07c2f85b71ca81258a6c11bf178d3 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 20:14:05 -0400 Subject: [PATCH 163/378] test(worktree): use timeoutOrElse for ready wait (#27180) --- packages/opencode/test/project/worktree.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index fe56008b0..bc8a2337b 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -29,11 +29,10 @@ const waitReady = Effect.fn("WorktreeTest.waitReady")(function* () { yield* Effect.addFinalizer(() => Effect.sync(() => GlobalBus.off("event", on))) return Deferred.await(ready).pipe( - Effect.race( - Effect.sleep("10 seconds").pipe( - Effect.flatMap(() => Effect.fail(new Error("timed out waiting for worktree.ready"))), - ), - ), + Effect.timeoutOrElse({ + duration: "10 seconds", + orElse: () => Effect.fail(new Error("timed out waiting for worktree.ready")), + }), ) }) From 17d4c366e6ae7778c3865421a4448a373e848cd2 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 20:23:20 -0400 Subject: [PATCH 164/378] test(worktree): dispose created instances before removal (#27182) --- .../opencode/test/project/worktree.test.ts | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index bc8a2337b..3975db1ae 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -2,11 +2,13 @@ import { afterEach, describe, expect } from "bun:test" import path from "path" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { Cause, Deferred, Effect, Exit, Layer } from "effect" +import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect" import { GlobalBus, type GlobalEvent } from "../../src/bus/global" import { Git } from "../../src/git" +import { Instance } from "../../src/project/instance" +import { InstanceRuntime } from "../../src/project/instance-runtime" import { Worktree } from "../../src/worktree" -import { disposeAllInstances, TestInstance } from "../fixture/fixture" +import { disposeAllInstances, provideInstance, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" const it = testEffect( @@ -28,7 +30,7 @@ const waitReady = Effect.fn("WorktreeTest.waitReady")(function* () { GlobalBus.on("event", on) yield* Effect.addFinalizer(() => Effect.sync(() => GlobalBus.off("event", on))) - return Deferred.await(ready).pipe( + return yield* Deferred.await(ready).pipe( Effect.timeoutOrElse({ duration: "10 seconds", orElse: () => Effect.fail(new Error("timed out waiting for worktree.ready")), @@ -36,6 +38,12 @@ const waitReady = Effect.fn("WorktreeTest.waitReady")(function* () { ) }) +const disposeWorktreeInstance = (directory: string) => + Effect.gen(function* () { + const ctx = yield* Effect.sync(() => Instance.current).pipe(provideInstance(directory)) + yield* Effect.promise(() => InstanceRuntime.disposeInstance(ctx)) + }) + const git = Effect.fn("WorktreeTest.git")(function* (cwd: string, args: string[]) { const service = yield* Git.Service const result = yield* service.run(args, { cwd }) @@ -125,7 +133,7 @@ describe("Worktree", () => { const test = yield* TestInstance const svc = yield* Worktree.Service const info = yield* svc.makeWorktreeInfo({ name: "detached-test", detached: true }) - const ready = yield* waitReady() + const ready = yield* waitReady().pipe(Effect.forkScoped) yield* svc.createFromInfo(info) const list = yield* git(test.directory, ["worktree", "list", "--porcelain"]) @@ -136,7 +144,7 @@ describe("Worktree", () => { const branch = yield* gitResult(info.directory, ["symbolic-ref", "-q", "--short", "HEAD"]) expect(branch.exitCode).not.toBe(0) - const props = yield* ready + const props = yield* Fiber.join(ready) expect(props.name).toBe(info.name) expect(props.branch).toBeUndefined() @@ -152,14 +160,15 @@ describe("Worktree", () => { () => Effect.gen(function* () { const svc = yield* Worktree.Service - const ready = yield* waitReady() + const ready = yield* waitReady().pipe(Effect.forkScoped) const info = yield* svc.create() expect(info.name).toBeDefined() expect(info.branch ?? "").toStartWith("opencode/") expect(info.directory).toBeDefined() - yield* ready + yield* Fiber.join(ready) + yield* disposeWorktreeInstance(info.directory) const ok = yield* svc.remove({ directory: info.directory }) expect(ok).toBe(true) @@ -174,7 +183,7 @@ describe("Worktree", () => { const test = yield* TestInstance const fs = yield* AppFileSystem.Service const svc = yield* Worktree.Service - const ready = yield* waitReady() + const ready = yield* waitReady().pipe(Effect.forkScoped) const info = yield* svc.create() expect(info.name).toBeDefined() @@ -184,10 +193,11 @@ describe("Worktree", () => { const next = yield* fs.realPath(info.directory).pipe(Effect.catch(() => Effect.succeed(info.directory))) expect(normalize(text)).toContain(normalize(next)) - const props = yield* ready + const props = yield* Fiber.join(ready) expect(props.name).toBe(info.name) expect(props.branch).toBe(info.branch) + yield* disposeWorktreeInstance(info.directory) yield* svc.remove({ directory: info.directory }) }), { git: true }, @@ -198,13 +208,14 @@ describe("Worktree", () => { () => Effect.gen(function* () { const svc = yield* Worktree.Service - const ready = yield* waitReady() + const ready = yield* waitReady().pipe(Effect.forkScoped) const info = yield* svc.create({ name: "test-workspace" }) expect(info.name).toBe("test-workspace") expect(info.branch).toBe("opencode/test-workspace") - yield* ready + yield* Fiber.join(ready) + yield* disposeWorktreeInstance(info.directory) yield* svc.remove({ directory: info.directory }) }), { git: true }, @@ -219,7 +230,7 @@ describe("Worktree", () => { const test = yield* TestInstance const svc = yield* Worktree.Service const info = yield* svc.makeWorktreeInfo({ name: "from-info-test" }) - const ready = yield* waitReady() + const ready = yield* waitReady().pipe(Effect.forkScoped) yield* svc.createFromInfo(info) const list = yield* git(test.directory, ["worktree", "list", "--porcelain"]) @@ -227,7 +238,8 @@ describe("Worktree", () => { const normalizedDir = info.directory.replace(/\\/g, "/") expect(normalizedList).toContain(normalizedDir) - yield* ready + yield* Fiber.join(ready) + yield* disposeWorktreeInstance(info.directory) yield* svc.remove({ directory: info.directory }) }), { git: true }, From d1640c075b90d4683fedc8f4fd86088b84277720 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 13 May 2026 10:25:28 +1000 Subject: [PATCH 165/378] fix(app): use session status for busy state (#27166) --- packages/app/src/pages/layout/sidebar-items.tsx | 13 ++----------- packages/app/src/pages/session.tsx | 6 ++---- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index f27a9bb7a..3aac5a613 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -166,18 +166,9 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { }) const isWorking = createMemo(() => { if (hasPermissions()) return false - const pending = (sessionStore.message[props.session.id] ?? []).findLast( - (message) => - message.role === "assistant" && - typeof (message as { time?: { completed?: unknown } }).time?.completed !== "number", - ) + // This matches how the TUI does it const status = sessionStore.session_status[props.session.id] - return ( - pending !== undefined || - status?.type === "busy" || - status?.type === "retry" || - (status !== undefined && status.type !== "idle") - ) + return status?.type === "busy" || status?.type === "retry" }) const tint = createMemo(() => messageAgentColor(sessionStore.message[props.session.id], sessionStore.agent)) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 1345e355e..8bc7e6a5c 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1497,10 +1497,8 @@ export default function Page() { }) const busy = (sessionID: string) => { - if ((sync.data.session_status[sessionID] ?? { type: "idle" as const }).type !== "idle") return true - return (sync.data.message[sessionID] ?? []).some( - (item) => item.role === "assistant" && typeof item.time.completed !== "number", - ) + // This matches how the TUI does it + return (sync.data.session_status[sessionID] ?? { type: "idle" as const }).type !== "idle" } const queuedFollowups = createMemo(() => { From 59b976b954decc44849fb9b7b185ea1ab3a37c9b Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 20:37:16 -0400 Subject: [PATCH 166/378] Remove TUI logo sound effects (#27183) --- bun.lock | 7 - packages/opencode/package.json | 1 - .../opencode/src/cli/cmd/tui/asset/charge.wav | Bin 590984 -> 0 bytes .../src/cli/cmd/tui/asset/pulse-a.wav | Bin 176444 -> 0 bytes .../src/cli/cmd/tui/asset/pulse-b.wav | Bin 176444 -> 0 bytes .../src/cli/cmd/tui/asset/pulse-c.wav | Bin 176444 -> 0 bytes .../src/cli/cmd/tui/component/logo.tsx | 11 -- .../opencode/src/cli/cmd/tui/util/sound.ts | 156 ------------------ 8 files changed, 175 deletions(-) delete mode 100644 packages/opencode/src/cli/cmd/tui/asset/charge.wav delete mode 100644 packages/opencode/src/cli/cmd/tui/asset/pulse-a.wav delete mode 100644 packages/opencode/src/cli/cmd/tui/asset/pulse-b.wav delete mode 100644 packages/opencode/src/cli/cmd/tui/asset/pulse-c.wav delete mode 100644 packages/opencode/src/cli/cmd/tui/util/sound.ts diff --git a/bun.lock b/bun.lock index 5da588910..8e7a63a2e 100644 --- a/bun.lock +++ b/bun.lock @@ -418,7 +418,6 @@ "bonjour-service": "1.3.0", "bun-pty": "0.4.8", "chokidar": "4.0.3", - "cli-sound": "1.1.3", "clipboardy": "4.0.0", "cross-spawn": "catalog:", "decimal.js": "10.5.0", @@ -2811,8 +2810,6 @@ "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], - "cli-sound": ["cli-sound@1.1.3", "", { "dependencies": { "find-exec": "^1.0.3" }, "bin": { "cli-sound": "dist/esm/cli.js" } }, "sha512-dpdF3KS3wjo1fobKG5iU9KyKqzQWAqueymHzZ9epus/dZ40487gAvS6aXFeBul+GiQAQYUTAtUWgQvw6Jftbyg=="], - "cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="], "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], @@ -3237,8 +3234,6 @@ "find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="], - "find-exec": ["find-exec@1.0.3", "", { "dependencies": { "shell-quote": "^1.8.1" } }, "sha512-gnG38zW90mS8hm5smNcrBnakPEt+cGJoiMkJwCU0IYnEb0H2NQk0NIljhNW+48oniCriFek/PH6QXbwsJo/qug=="], - "find-my-way": ["find-my-way@9.5.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ=="], "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], @@ -4567,8 +4562,6 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], - "shiki": ["shiki@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/engine-javascript": "3.20.0", "@shikijs/engine-oniguruma": "3.20.0", "@shikijs/langs": "3.20.0", "@shikijs/themes": "3.20.0", "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg=="], "shikiji": ["shikiji@0.6.13", "", { "dependencies": { "hast-util-to-html": "^9.0.0" } }, "sha512-4T7X39csvhT0p7GDnq9vysWddf2b6BeioiN3Ymhnt3xcy9tXmDcnsEFVxX18Z4YcQgEE/w48dLJ4pPPUcG9KkA=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index e9b811fc5..121b34c3a 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -129,7 +129,6 @@ "bonjour-service": "1.3.0", "bun-pty": "0.4.8", "chokidar": "4.0.3", - "cli-sound": "1.1.3", "clipboardy": "4.0.0", "cross-spawn": "catalog:", "decimal.js": "10.5.0", diff --git a/packages/opencode/src/cli/cmd/tui/asset/charge.wav b/packages/opencode/src/cli/cmd/tui/asset/charge.wav deleted file mode 100644 index d9597899cd987777eccd011a3df4b0e8432ff777..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 590984 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

EQ04HfPulR<+i3d&4@6-w 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 g0Z9k0Zsx9;9dJq7%Ur+7oUQeG;A! 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

s|#)S0wDX(hBu+J)f1(HnTsAJM1L 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 IvuB!Jcn>1cki?&Za3#0(lh5$XxG|%wV9BLY5 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
bHnbYb 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 z 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
`-5<#q-Pq?&9-3bnZ*tLGCNv;BISO z(XQ)Va4s8x9H`I9rv)mC!+YPA#Ni(yw_GUx-S2J#jHg8T(z!Ob9N5FK#u zP?!_UcyqpqWLj^`HXb!>F+9>=*B9&l=xAED7Ob__25Ztai`Dzp7gb+XWVFuvyc zbKh{IxF5Oc++W;<+-mN2Ag8#@%fs;F z_!Rzr{vLiY|0D1%c>HVuQE*$ZPQVhp5`>Qn1SiKs$NR?jjt2?L$4>|m!Xe>2VWjAV z@QP?c$Oro9OcP%by%5txCUK~E`NT1C$wZgfY0`UQ|Ky&DzR9|YNQvX*HA&&5Nb(nO z;zCGXPA!z^r(Q|sOJ$OeQpyxac5!NNCnn^$w+- zdWljFc%t;Ef|cJ?w#ut2tzwgkp_rzsQ@8^;jaqp{(W%^_c&nVNIIfIUECkkwAxf_ox?1Bs}TPYI(6{L14PWDAQCcQ2#lkSq;ybZCLAa0CqxsECn_d(PTZWBKCyPfX(DPuBnC~Ci#x>E#gD|R z#T&#C;xMsUq!zV{%0-Vvr$ie?nW88WN@NhW257> zqmsIfaex}lh@|T16C<_s4;zt05qTLs9O3DDwzSH z9b_z|l`=llEDRVui@BP9omoyFVY)E_SjQOqSZqc;E0alJe_?KA6IfO3Q!D~U!rH-E z!EWZXvwcS?oD-wpIE+!>(Uh@AqtC{y$IN5r$JTN!V-4IxToO;gy}{c7T-}7cmHa|} z1AmMkE?6sg2wZKT<15E^jMK-d<12*OLYnZmaJh&mqKR&bmIJ>Zns^6rbzz8U69p5? zCo3mtlM$24B`+sw5?jf#sWTGlluEKxx_N3yN}F0FnbV`W|P_p*G2ySznl zQ=X}W0lL-03JQRK7pc}Oqk%K|v$_B{`)gItHDtgA=8O8emZ*X29%_#2@LIXP-I{H=7-S?CO$fy2WVvX4z?W1&x}| z0_(_0P=jS5I3CbByaf>Xd?^(^MP-N{R8iX z^#Vv(5aJR1C_)eKL@YyiB8w2)k@m<^{d zM|2Nbjz(h=Flm^hKySx#%xjDR(~F76nlU@Df!NpB0_+fW7Z!=Tg^d9+vy}k9>nz|y z_YS84x)`8wJe&*8jPu7i;v;ZD_&8hwJ`tCRPr}W@lW`01(YVFrFn(f!3;IG zo4%ONn--fgOi+{A_}y4;Tw^?Dv@xa`>kLriK|`}4%y84d(=Ri;(EA%!=oNZbeXX9V zyQ_bu+pJ%wOVyKf1if4<1!{rqx~qVeWrg;NE<(FQ2h+~i(KLy=KN_O$mIklesnG){ zsYn~6p=q5pom!BlRx47MXb06lwe1XK)Tz2r^(>uSy-Eks?9@4E zPU-?Qw{(e`|8)79Ux1*lUbkN}pu42u>z-@Wx?dWEzC}aOGd2Etr6xv?)@JCvw6pcG z+7*B+@@D-i?Oy#}?Q#7D?M3|)?JfN`?IV4y_N9IRKzjw+&w8EqhaRK*qj%F4>qB&< z`XnH7b!Ga+z_<~Z+XL)7uKTIK3@}UY0X@fG>R#zT>YnO<>F(*vb=UL_y0iLr-4T7i zZnvJM+ob2{R_Fz~x%x?6re3Z~)N6EMdIP{P2k9O3Fg*%*7<4%Ol+Fg|ZRMaJ(mCtf zbnf~powxos&=uu_j--E~3)kP*#ptgAN1xNB>5l=QbP%ZC?ba>OZwEeoi*BQSlWr&Q z?G6KR7TCTC%sm74eFDC1ss6C8MSogH)nC<3=pX1T`qw%K!!G~_tJkF)26c-KBHbG?xReU22QP z90kg>Yy(}fya)AJcp!I>FSro27W@PB5^Mso!85^LkO$y35FYp?BnGH#orVyhJ&^TK zZ|H02E+`*b4Go0Zz&69yz&^mf0a_gkECfCa{y+FL_$T-TTm(--kPsIUn-K$uHwYi( z7-AdJ3t5C*js&6ZA#+h($Xh5BaQ(?ZxuXxE)}X(lUZH{F51Ih+jz&eZQHgxwv7%tMw?VI?#+$) z?f>`mIg>Wc6f?=b=j^@KZ}EOXCBl$SJO(>Tc{%7V@TaZgQPG7w zH!_L$0~y47h;-l`gKD=DN#%`3xV%;f!A(W}aO1qk+@HMT+$+45+*Q2M-2U)0=kZdw zF>V~_u|GL`x%r$?+%=ra+`$|_r#k08hss&X`NnR+IRW{))7a10jo3@sOm;)|CzhYJ zn{}BrkTr^>XDL}F%n!_C%*D*1%qmO`^B?0UV;$(7YBJg~is*6rN|-3-(nrxh(IoWg zwC6N2Z7S_L(1*HHUsBzaN!0Ze2{oN^hw^|th|-fBC)-Ge$csr0$wJaE(uw3uQmrIH zdXYE{XZ^-WQ~YaUQhZDT06Os%v5j#grUkmdCGcxBjGc+RjaG^DiQWj8L>h)CMxKC2 zrA=sQ_&M%a4%okxcE^Bl*l?rPY4_hSzNWb9+E5_dhB-)}A&gctgKho?3TW z7FzpT8dwdMn8j&+YPn}#XW3}(Z5d%sw=^^d%?k5JbGYKHxu{|_>=(zHuU2$1AFHTq z-c^xd-dG_wudP5}G2wl>c?aAcsX)zFDwO8u71`#06%EayiXQMaCd1iivw5`nx_LWL zM4p@3mXNuDrMhK`WwPa*<+8AY+QR&`#Ae? z`+GafQOz;UvENbbU^u%t=Qy9juDPyjxGUfF*Hy{g-+jUT+g%Cv<5xVTo;uig>=EY2 z+T$zm@3`1I%6rP|^VTO;6Q2l`Z;bDXkK}LdKj1I&*A2`LdOftI_Yc=~3 zi_NLaUdOq}W^(nM<=m~D2<$heLZ`iqSIF&&)Z;xx4)F5O7;gvKADj<2!1=%f)!qnH zEVzMAgpSZNL3h4Hcz{1jSjx{AW(mT=v4UozGlCVOGQo3^R!9OuMg#F0;WY6b;VH3M z_+2a#Q6&vURU{)|8o5d|UvfrtQ1ViAUs59aA&H7S5|NlL%@Ql6^~E{TcH%nHKH}!m z;o|nvapEr0sp4+ZnXqPyJ4Z^eB?hs3o+Q^W#MQ?Xsh6F(FF673Tn6HOM5 z6EzoB5s8IfVVU5D@Ty>@aFL*fu${my5DCr-{_uwjj`HP#QTzgaHh(GKhSuPpLrc-I z=r)+RHbF}e8*%{If%HNeAzY-4_msDeH=9?Pm&5za{llHk-M|%b8*s02tekF~og5dt z8D|~a#!h2zXFp^$VE18}ST5!&)=Fj$O9l1i3S%e zsq{`?q{v`V~g^hxY>qCJ)1N z&{OE{?>XjH!7S#VyRG}UTjw6<4!LT%zq`opo31aey{?n4#jg3TF|MBQR>jrAg}7?L zi8j~u)tTzL@6-WlMdR8Dx2v3Ku6fQp*EDAx*Fj)jnL#^6Nzxiq*VBJcWsIq`V+;pDp^G+ZRUM{$x@=-lnn|}wr$M42(EU@yQ2$l+32xY?O z!i&O|qGqCJqHm&R;xXcf;;^{BWWD5uL?x{(JqvW+Ix>yynQXhPvz#ISA)hB7qHxG7 z6ay9GmG2ZTWj*B-)k&pCg{mg0=cw%JV%2C(D|NBvth%?Bqj{$tuW77%tGTSp)N1w1 zwCnYMwLX0f-C)C7-E+fVU1myU{o0g;dUMJ@zphla|Y81R7RWn;cs>=AZEty-=?q=qvm1KTO zqh&eLGP3CDEwa?<1GB26Ps?hWzA~#*`u40o>4&lgr=Q9io__ZK*Uerr`Fc*OZluzPZ^`T zXozU58P;j@^%8BK{-EZBE=42P9aFCXe>9=l3-f4H^+df)nWDxNOH_jtzm+fL)s(sN zm5NQWZ*r$JMc!UIQFdH%T^bgLBwfS}Bu7N^#U9~pQ6nKPTrJ2Ieu9c8Z-^z{Jfaxi=52;Y@iW+1T!{U~mU=qF zYx1RMyt{>`!u8BO+SSHwcYbuufUX1C>2&UNEOX{L)Xq2dyN+?tnc&;0j{COD_6fG3 zc7sh~|7-ndJ7C>p8wQT8T&uzswwSFSEqAPE!8g3dGQm2*(#hJ}Qp4KHqOsPu@T@f~ zWNURx)Kc3Lu{5j;a~y1eSX_Bro5QCFsGt!s~q;pVy5 zx>vbT4;6ZVD?OPQ2fG7(!p^t>x8PTy!`RxJMihBp6Z444J~@Od-}Sxl5An|oFd!TH zDlj*O26sS*yi535h!dF$r=n_+^-+7IbL@0f1RjK6@r?M{E&34tUz*58WDel-BeuMMU*R^CG0KQFIXac z$UiM8MnCWq2!?795!!%P3+cn{#hb>N!QH^#&N<1t!hXd3!1}?kF+E^dXVOy{23iw( zed+*O56T?sMDk9`YSK0G@#JUHql72D|(dX*~6n5EB4AD2uoT~Lxy+Nh+c zgj}+>LWV*7VHS%=F9npNTMTGYNraP}Af!^)qEd zI>LyOE2g6*CR0g?1b%`}C6h|mmfS6UUBW8klnyBCR+?Y7zm!#ODIHZ_7kcU&$}%cS z$~IIq1VYcg^1+Y>{~mtJMwaIlS1gsx8P>h#Lsq6mVVh^!Wpi1i_EFY7_F}8rG0=9* z@!OW`9B9AcEVegyjdFZ)xg0~?bDX%F=3435?owiD?tJW~yB*%a0|r>nLhoEmK?w1i z#6`TPue}%dS-ji)tBD$cEZ^6_8{e$p1ivE058Myk2n-Dm2vQ>?=sKMVb&Pfmd!nK6 z&e+jNy?BS{zc>+Hnm80o0~*-tBpx3}I*{O!TPH7(-N`PLy(AZ<8F>@cM$V&cqr9Qj zqmH5*sY&`O+FnKuy&AKC{+c#g-^L%Ay=JH)KTRtx~uLg#;P7E z=Bpkn)~X&VwySO__NXo>_NopkcB$4YHmPPRmZ|zHrm7k!2CC$W<|~5(WUuV0xRz{y z_=D6f8YW!=r!X#@%ytS%5~Xmtc)x%oRtV;aw(;FU7JrCvKKfo@Luv|oBYXG{c_B2F z*9%?2y@-@>$S^kV!Q06`!nLt%oQABbob}A<>>rG4ECa*M97E4yUZ4$OxTxFcm8s8Y z6DSznVn1a6ZTo2R!8KT5t!1rZ8E@$fo}bAuN!?y9t+-v*qrA9uTN$ggu(Vc*taPMl zP{~f?Nz>oXUt^WScqxBnF! zdpoFL-`hR~yWjRK*!#A3!Qr<93eLS9RdDC+^nwp>R~D4N-Csa{ccUQfU134{cfNv| z?^GX8yleIG=erpn`R`AC?DYQM$Ib8MpT5EO*MHdeX)-YWpMA*ttSVUWc}~Hn&%X*X zzcl%{{L8tIrC;Qq+7@nroI1*9_1DFpH-3cz|83=$dEb!2z_$a1bG}#l8vFk2>#84v zzX^Xvza9U1_c1s_uKjENcXScspSt+d zzwgBdinbdk6!$PSF-l7$rhg?a)9KQ;CDY5!m9~HyCxMK4TSecBr{?D71D47bnAKbR z+IY5l_M}biAnYWVY+9V9&d<=bx##}jKJEG9*@1n;mf;`psowYAVZ=M4yYH>9rT?wJ zR^V+QEBH3347~*x=9_RL{5pa~UPeoz&trwL$MI*N%DI-f34WSOq)nt#kixW|GLo{1 z+KIY^R+BcFuAmQOMCq-eD_9w{iX!$NmY+S1{f*O(lh4&~*YVukA;<$>HFOPYZ2>u;7`m(-N?2FC;;og&{TV<=MOd!deG zD4QrQD_<$9tJ*2^Rj-vf>Sn6L>YLzEPFK&-Y=InFzq%0+;CE}UYaCjRwxMp2b`|WK z-s^BJM_*Id8ukS9!RLBjU!?n{=jy}yT6(3SpT4GHj=r5?tG=({lzzD3hW3#Yc`cL{^`cwL<`dNC0zPbLFj-@}Td#jtMTdk|A zYoc?3XY;i7h<1Rs9sCR~%?-^a%|K1ICZ>L@-ly)NuAw%mUa7{b`lvk066G}ISfyJ5 zJYmInsFD@(F7iS02eQwyOj%ReCg}w!CJ{@!N#;r}iGPFlsh)U{Xoo0Y=n#^G&4gV9 z+XegiCHzugCFY>>AcO8Ha)rl4Fm4B474Amv1kMM}X*P#rVzp%}Sj$;mnGcw&7=G}% zRAP8&Bk5_heY8H*kKlo!QXi1(QQV}7_DX(Fu7SSWec(NLVl(3Dv2(EjQDby# zL>7G$?iwM7*M=L0o`>cH6TusSMnS^AAW+MH-#^Ef@I4?}K#z4T(b4t)7sJ2)%w)> z4*I3-Ew?T2&GjtZ%@56gD_Wb!R(uB1#ITCZH4xBW!ADQWgW@|po*CVYMJ8l)fF8of))2FCYmdo%Yhv<%%X!y^Fhl9tJZ3@ z9tRD6jx7n3<}LOX_B6*=IMa=F^mHa29ym8`an*5EcmHtx07b-Nj}Ay1FFij%SFsA$ z;&r_raEEu1_ZVdG4e*%>iT|+gv%fDmti^#Y!OsDHXiKmVxTCwnLYOao3#Wm~$Ppb9 zy&lVmEsDEhZ4)=+qU7R4ak5?VJV^wdwjxps%2_g>I)n0;+Jt(B#-`1nf2TEN9HDcV z;~C$WwU~!lapr&QSF9?W&1^rXH|HK#&0WkhahoG2c@*?N->Qt!i4AwB>0#(+bmmq-oP7>7&!Tq@PV+nQl$L zm7bnakv=GcpRqNgcE;0;z8S8JSsBvIEg20mPh||qyaU{gR~cI~KWCiE{E=}h^LNJc z%%2$_Kwe=**sbkWIq^72$sejV$rEEzXmC`JY zlj2UjWZ0Y9+0ZQ2t}jnnrC*hz(Wj+c)jcw_)b%lZ)jIUUw2Sn14Wgf}*{=(!({&5f z7ql@|HSGe`HBCraT{B&IMeTqTjp2&psvmNts=a)Z^1dvo$dye|?3EVF>Cz7Jsgj$r zKVq${g?N?pyvQyQh`LB-39pKO3q*j6oGv=T{~{#us|bgq8wAe~Gd~q+&R@nmfSR~I zq$#%(a)@)17h^}c1K2&dcUb2*GSFYnV)kZ#Wn5s@V31hb>3x_uoU?n-D2!{=esl@- z3T-xpLHk7>Ol?HINjXMBDGbsW^2Fp*(zk?$)HpFGc{=_rA&gf~ERJoBJE9m=scy0N zk?YaMP^t1^CYup12_Fo#4@-dqxFx6yaf9oF>jL2bD=;pw-v8H+_`CXd_+I!_zDB;2 z#4VyKygnX!uXsCqt9p(2HSmYl0kzUY>^Rm2c3Fj9`5w%z1qH`bx57Qvo$nI4 zJGvgZtj>w9wN9-o*ZIZy*|Eks*U{9O=7>7Jz-x4weV(H>q(nGuRB*|?w-2}Nv8UKZ zgEy*<{hXC!pJp}L8e8w$c-GxEqh*%uhNZ7qt$Hw z0cl;ItZ&U9tWV7Et@q3Y);s2}*1P7v)<NOFUfx^(3DD!~}qG!xJ>b)J%=Ii_5v zk*Mg}m8!*Buga$#to~2;60+=aHO=)KHRtsX4d2jKJJoPh`_T~4W~a2*ElJs@D@-x# zQd6tyC#TNV-%7o!2UCH*VOnj&+_Wi%tI#L4r2R4|(wQl3(rcznO&^r9JAHY|-Sm?w zzaUoGn{G^DX9QEU8NAd=8M@SZ8I@9-Wq=Fql{*;{bInbN!p7J^^J7rs1%+N2bz@SdsZ}^)! z+OQ|JlA(Jlu4krR*WXK-rXL4=X&L<7j|~TOqYYhkY(r9ePJdC`R^L}!qDyM#=}u}; zU0cmjtwmi;yGs2)V^Fu%+);f}cLDN6sq&|4rn0Y!s{EncqUf$vDc(bWtfeAHen);u zR!Lq}c35^ripZ)+7f8=btda~#58zwg7fZ#d;`O4nqJ+>XoF?ohECm)>513&+;ZNgN z=6^=_p_Nf8x*nN?RPes@n(-R*_Jd#F$6<3jb0%|6u?yL8RvmV4);`unW{gQ=4q*;t zJYig;r!d&`6`SOX)ih}%>Je`zB{*c^A%1nBa%M#s_#qpbo zhH*t=A6z#>(b8DIXuH_m$mOUSD$pPwBZ;h!HU z^tt@4e8c^Bh@ZYHL>J#_=xiFi&0$Xc$g9K~dG})va1GW3KjwLkWqaCVSKVJcjX}}- z-eq+Ua?NpLPL_L>^Po!y9J#xWch0_!(aw;a>O5dS;b?Ae?XcVI_T9Ga_SUw>_JGxH zyI|dK8)5Bi(^{#vKbHH}1C}|~A(q@cI|M`nA) zF7vyJDdtNRonQ`K$-JP{E9gh4=XlR6u?I%70+P5Q&J%`Q_WS(+2$_hZsuv`h2}k=?R{$gYj&CW z7LBE~r7QT4R$A^`9zf?gWNB=zV_jyQ15W*WR*{Viq_GaR%eLLHGb)E{$=ddF&|4-u zjP^l}x{kZxOjbBOj(IT2{^!hhHg}0#XI%4K0=LOE-`(DAhW%(SPm1TcXRD_sM#4^E z(=i2Jfo;V5<7DqAd@fKQJ>Ey&F+^R$L|ld4Q=aetQxyGaaBe#4ZynGEo&@&8E?5Db z``y7>AxY>;XnQCxEC`JjR1 zYJ^&k)|0l2cAkdO60~;oZuBGcqjWFbMQ_EZ%h=9X$|z>MWMng0%qcJjzs_9E^fR9_ zYq7%2sjMojbFASkBWo*51e4g7Yzu1|o6kPLuE~B5^jj-?2AmtVuyZ(P!4vY3J(%-_ zJ%dxiUd?f{cXLAQ6C4uf5{JgQ0nENT96IMNhs?RniL$SAaP|dG1t@BMuy=4?uvc*M z*;6_D*aJ9A*v&a3*_oVHY!)YtZDA)_&)L6OhuHb7S?pD;4&XV_u(MfCmY;bSs`N_M zdQcAbV2W9C<}b(r+|N9~=noz#IaAK~$|$67WNd)nu_Zl14+9(U5;%j0(gbt??Gf!U zbpmY?RYp^SYUl-JEOinErAjH+L3h%ZG90*p4Dtc;InbSSB>f>dlXD=|O+v~`<|p4K zIwr>?%0W@LC~+h%O*8;q-M3h)_~h817&A5ncwe+wz3ATPXJFrsi9CrU!aXB9!{%^q zcy9Pfh!gG;IuvpStAv&Xp9E#W9>EKN@<4M?JpAyp0u%iE{3L%R|1RGnc=mLI4o(@- zhnP(`y&PhN_mEfVt>MkbU*YZWfw&RFu{qcpOaMgVbDo=?7M{+YfAB1w?Op=PQKkEm z>xQentE-`d{EZna^y4YpmjYPN>9AJ#JK zLhA-=y0y0T1Dq#jTQ*n>mim?tW{Y_)yw)dF8b?JnXijrz2S4)DX zB_)qdT}#%P3?;oxgek@J8mR4uO;3!oP1}v#OrwpJObv}}lgMZ@+Kb;AUlw12WW4Rh zCB^fMLyJckn-_OBrWe;Ya*DH!c#+chr-*BOSwu2kFA5YNFY*-cD6$o=E;1J{0Osus zSQCq^#bb(G#iPN0JgO)LnV?ML_#z3Um!?6^NG;>?qPE7(MT3loKnr}SXp`|t(OKj7 zqE|*&QH7CF%rvDI=b4%ncQXwuo@-iOeB5-V_@n7#anuwn&MQeZ4le0x+*Y#0_`2k( zF;P-stXHZw%`EL{x>~x)gqOZE)hJ_^%qnXSxicF}NabHj+LtRzca#q=HI`p2ty~c< zU0Bhste|3VncnOun_+HN{?@#^+yGe&^DOPaZ+Nnzij{2MU>#<*S)ZEQ+0ra$VSmQ4 zJ1vv!J*;2s_pEgs>9&K8%{HntVjJ%qYyax}VQ=he?KtDQf>4F zdhQ9hs$&z}M<5>p#rt~};-5VZyd5^c`viOEt&i6yuHk2iN?wKUly{9!58D2H#Av_R z_tn4A*8)@x`GKW=T`=t55S$(e1s#D=p^?GQp})a~;hv#W;ddc^q{z@*oRYW$`8pYiCy8wdZ8Daam;4X<37?WxNOeeS zNQX#95(U^sc^xw2o zbRF#FMu58KBz-5Nf?mSNU{qp`WXxh7Wjutw6OAPUtzTE>M%EhUN7h3opG`13v$I$m z*!@|r*&A47&V5z`j-54)qhO!mv}XSUts9?vfZdGyf<2Avf*!7fQ^0M&34``8jkki+ ziFcYallO*m05T1p@L1e(UKW>uwB}|bBf0I6CEPK{e$f5h;2uRja_=J+?sp`~bt5v+ z{Z-;=(B{1AXdhl{bUd#gx`;O(-NIXd9^-8Q-QRxn8Sf1Gg?9rr@t&Y=m>0%)-%vL4 z50xV2r~z>R(*{FpBYw0g5=Pr0F|-?!K>I@9Y$!tFkA~X`NF1GtMA2DD2%U%c(1nNx zU4q!qWxyO-0bge&@(tE|SWjWyhW+eC_}o!+F|q?)fUHL6B6HCh$T)Ng(jOg%v`0rG z^?+}lh4w+DXcvTnwn5xTW8@D~3wep;BKe2`*^fw&6$l%dh{Sol5scRebUx|GcOD0M z#lv~Gc|RaK=>cyq?k+I`Mk&s=_Wy1X>^;SIPUqrStA^aqd3uKkj_) zEABwe&`Oagg8z5~k6eXMot1*|FT!K_~Frl2qa%q5%7 zVzO;a7ps8zjdhKApS72HoVA#_o;8{|jn$bsfK`XtlBH)>VzHSrmJd#%CWeDq!1xIq z#pld(jC|%k#v$f<#zy8s#scOP#sqlQ4Py3VbY^yCG-I}9)MPe;Yit9CmRW}(X4YhI znbjFoSaC)TMu<_H;bYW?jH#v!52FpNt_+OPpW$VUhCbYMMuf49L1J!$f#C^;0F?8f zfnsEWg0&_y%4p7%GP^UYGeQKnKzkVnFY)+)50{cD6Dp%&796^ z%sR{(%zDpS%nGxPvU1ojSOeKM)@HT<^qlqBA@Dg?FOHmtfL62x_aJW`*Tl=`W*|211kjdVLk9E0$X;Fx^cy^Xh49?!is<>9 zkn#Kv$W2hkk%2?eS+EJ+FZhI71VVl-VNd=t;cosX;cvcHq!El44HMiEofOc;RzXj3 zCE-!=RG~|JL)btP60VUn5`C2{73rieMB}7P@eOGQaYDLD+*bBkyj3QW{F3#O=;ga4 zqvhWv7vxf@N8VdnQ?XMzOHnAjt>DXoicYdR$~Cf?%4f2x%9zZhtS!${O$Hv@DfwR2 zU-?IsKtWZvQq)n;QH)ieQ|wd!R=iQOm40Ut39F`rF{Z9KPKRxCREq7YV})fU3IaxyBY^3KUKF}E!OQ-r|2%JE9;)9 z>*)&BEp(;o_BveMRhLxv(s4C?bTUmpok7!Am#ygyzR(`JTAI$fdYU%6hMFe2Mw;5N za^ZcQuD(W~tF58vs%da-uEwNI(-de`n!8$|=7g4^*`$p?4uDHNNNZHL(0*2DX&?OLly!ksAW?QxVv2O-2Sq?} zM)6j$OmRRlNHJMaThUa(R|pkWd71pF{Dyp=e6@Uy%hh;(8JK0CsK^e>f zW%Gd@*-gfWZnjApm0p#8kuH~>miB~`Te`HXG$2WrzLta}dnF$v<0VHWjU+Rm``=b# z6ssgx#cuHm@l$bc@lJ6zD61l(`r-l+PkcmFESfI5DrzlSAySBXi=4tN(PNlbZWq26 z{wF*nY#^L0M4>lcDi8^83CaZP1vdmk1*-+M1bqc8LAIcXAK_o)zvr*zALI`PQg97^ z6Fv*5NyTUhdIP&Kdyg}|m1>BBIK*m8`Y>w~|1yaJpc-MJv zpxPbf_2tb4wPSZ)jGN2*#HE7v`X6@|_a?U;cMDXvaa@eklKXJOU(>2BaFYWQ+@p1L|*Dx1>4t^-32eSpE z9y1esIUEKZv)`5?EB{%?nmnI0Czd2{C3+@TB{D$!9!wY#uM>{=ZtzizPArYrNOS;N zswn<8{yVlKel#`+R7>gc+?XTgj$Vyjjn0nEh&G7TjwZnH`2=#cmPcntT1IO{s8J&P zERqj-LzBTjk{gbN&7r&D@*W2d=pdr6 zR|YiJ&)(1Y2Ja@kjkhB>kJ|ig)LE9Re)i%%e$2JLO=fiE!Y<+Ea zY+XRR*2Z?$*3@>~R^N8eR?D^*&Q`l^Rct$Lm25j~d2pKt=dDU`e--$Ab=y&R%xQS+ zC0i5QP0+qQv~{$-1~2j#+d$ht+bH-RlWcz5TpQKC+$OYdwi)dE!KZx2*35px*2DhH zHq!ptHrrloTW5FM4%*|kd^=))Y1i8S*sItv(CRZB-Rv622zyP(Ovrv)Z6D#-YoG79 zVBhX|WWNA?=NFE0dyyk(4?re4->GxtIBPgsI@>q~I|l$MXtHCQbGhT9bGPG_^Sq

geR+JO6W~IhVQWK|iUJ^R{ak5Zq@vozUZExDPs0+*h3q-7lSe z+`pYu-7e>PH_3I!cZTunS1Tzx%z>EyD)b~>c8}Oi!7|+{tiHPj*4f=28|EI4&2Z1f*0{G|``xFpOYR5Q6Zco_o7;w2 z+(|6rmO`d@9-iW9hSvh;WIN9Ue4uA3KE<;OUk+^l-JVDIIZq+}&|}8Gcp`YY2la+L zDPA5{+p7n!c1>)cw=FiwI}lsyoq}!muE0)u_h7fY7qHjf$Dm{WirKvuEaHt~T!N3Q zi8QXs~Mr$M?e9)c4)n)@Sy1^##0rd<g(j=`&#;hz6P+W!OHXr zd}_Fl??Zf4AIBFUm_8dp_5CFhL;(>d9uWj_g>Vu_hzepeQA8{zz7Z3N_ryTrDbb#| zP1GSS5^2OyLO|>!V%{}`)jOZ~=AB49_6{M=d%F_5yv>NE-fF~nuYu_8<%3f^;mz^7 zykhTPZw!CyHRHFTa-8(u1$FBQd@)pz3EtUwU+*xyrMClK4Sca0uO4SXMe*T2%m`hQ z_xOA4Hhv2`fuF#(z@%Xzz7QtO=?EIdx#yz{$kg$F!lyh!f)3A4`G9GA-)u^gr9=G z&fWfgFX)1oKn*_Y-AojEpAiXfkjNz}`})E2 zX$^6}cboVI|C^{^?Q8Gv>RSc1{F(nAq|)I&NV)MZ4vg?W3LNy4f69bZMLu`w;IFQzlNt{(}sj>xm|DQWCP!lYip-li5Jk zo&yTpSBYhi*Hb_ml~j?hC&!Sfr0e9Kq$K$SsUwAe1oal=GRk&x6>2GY9<>JL1$8+E zr4>^8(Nd|$Y163=+B0e`x{$VrJ{)!{`80%)r1fTWp&w)%qL(rp^#7gFCNtMEt}}ly zLd-N~Bi2;rQs@*sXK`2zaH6zhA7`y+yIJqq_1Rp`N_I!iNA?Cdf4<|4;IM!(+XiL@ zE4YogPyTme<$mDS<%xOIdHs18c}IYwQ_j;PIY?iaE^I-rBCil15<}{sH6Y=A40;zm zfJV?yXahclKaUSS0{(ye0Dlj^rr-^KiXh0pAjlM$1-%3Ypm+`tUKVT@{t>(r(uHJE zHDO&*f8iw2a&YRL7k(3c5i-RgVLh={G)~+?v`_q>=rv5s{o)Iv6v+osXNglZN5T~! zljMruO4@=mXrx#sT_SEE-6tL*y)IrVeJ?&HEfqfn?YmKmO5)N~i9%Kzv^H%eon(C_ zqhwvx z#q!4ziTpm?-jWF9`4X=Dtb{H<4Dkg!C4_8^#44LF`70YQc`q9vxi4!cIV-Cr*&)+Q z7Rgw^X2irDWk1AKW%tA)*&(qPe%>$Aq2kNZ#^QBSm3V~IFRCy7BtoTUM5U6&kmk`7 z98Q^{9&motN!|)=;yo~f9xa?Jt|n|Oj)HdYx!{>-y1f2yz_ zKU*l{7YV+i+XY+DR)Y4ZpHD$g@$Vr$`BR~{l7U=6e}hMJ1L(LL@lGNxZU^Kb*Uf9s z-OdYeYVnS9zQd$w7B|2abC0mEa@w-HaNI0AdnaoZyFN?LE@s|gEoXLTWiV~b*Nmmi zaSR!g%ect6NN)k$wx4t-Z6bJ?Nc245uD+vY)5cLBP?^-O)YFtQ*k#V9l#)4=W#mKP z{;p1bNP0;cMCwn9Cf&*X$rZ`^Nk#H^;&NhMqE$kb_#3|;pAjD%=f;zC&v5nd^6>XimGJz~-;g1+KJ+G7KQt|94{Cx3 zgKq-eg0lnkU`F6U;HQ5^V4c5mpo!n=_xVoxFZo9J$M{nHslGCw2^b(pi3z^pL=Ct` zMF@xY1#!*0m00Ka1!z;u2UIKIDU$Ia44eTnu z7u$_5#unlKVgKRXux}`o)}bXKSp>ksGcrZPRs|lVfZ)+ z_p`7VFhXc}3dYB)VQRbumW%g>nmHcpgfGX2;RmqUaAnwxf5Xn;1oj-4;3m8-p1=p; zdhc?)x%WIi+WQS(?xp zov)qmiEp(p;ivm2`rG)=`q%m^{2%;j0byW7pikgL;1KluE5LbBIXEsjCwM9N zH0TZTLiIxZLW@JELa##J5E^b4?i1b;J{m3#+ri~sJF+;kH1a9(A!3LsqSK?Jqc5V@ zqOuq%Ha6BJb{|r5(YPZvJl-IFGrm5~O8kxwO5`N2B<3cl$v25U@T@owG^}{?PO=+` zMLI$1M+%V6lG>3&qyyx(WEXiixe3Kg-a@GjXPgz3>eNEYGH_LYrcR>jfkH5e#-!b+ z^`f!pM`*o)L39jQ4o><~dL70)`cj6N@s=@=A%xCUKcusjzc^Dl0`57eoF$wETm_^IcIK9H zmvhCuo7|Q>Gj}?qKOKR5g!jCeya4YQRL~E+wuqlM3DLoMyESqX8ISx#HX}i(s4}z= zsfpr97nBd)!#s2u+6LVNbft^vY{-?`h!&zp!IgL&#n2aM9Q}&2_$E}yccMyQFzNXT zG>uQ=XYkqlOvtcG=kxd}aI1xnWpFNVBG(*}WQbdnfc?9ns?BQ;B;c#T&Ciw#1>kV-IX~o7_ zQPvq&K5G!TulX!F%*5U>r!(g;Q<#~|PcQ>p$XLwC2H$%jy^y|;KA)aWPl1#AYua?$ z1ezQOk@u1g$lC>QY{q8 zLeO|lPIO9e66W}6@F_KqE93uS*JE>IonqpcIeIy|B-$aWjG80YBg-S5Bf5w+d=F|% z->@$158Z>QXP;2@5E1+w+!~w`tQr)6s^e;4QlMu51;XqVn1c22H}?CXn(T*HR5M?7 zP@?^T{p3ob9w={0VK2BD^3NN2>v`?C4L^YI$2;L&a1u_%Z(?_`3D`6&4a>udJZ8^6 z&k0Xo&j^pqlj`~Du5cf4pK}j$PjP3t>$ou&$$cL(4%fQ&yZX7txpG_$T`?!$Rp_j6 zo_9Wgso8GlFy{==NOyPYf&W8sN}N_l*zw6>f@$$v$7#n6$4-0tE<27np2OGp<9OpB z97Z^Mg+Svdf%$WF=QQ}9yWx93a_(_j;ZKRn;%wm(xn=;L=#*=+>yPV*OXx0ib#$}b ztKCiA&)xIfRL@O!E05Q`)>Fgt(KEv%g&pQlECva~t>8?(32Tf0!uH_lxE<6=O})?Y zU7!nhL8fCH;t}Zmq_BgZObqh9As+bBd|Llf-%`KPSMG1>Zyz`cGapJo9{ew`EchYd z2-XPp3GEL)4^f~;Ffnu_{4<0`T85WE9|42^>k-jSk?+xXq&@H;9>$E(8u9LsI`<-` zOVo|;N?eHZl7_^}E+5W{4k%eu^`|1-My!5NND%$soxf=~Kx)X{J;zTPK|%b4Uwh zon)Evi?T&>j{KW^tUO!sLcUO;Q+!m+Rp^wT6_b=%%KOTNN`~r-vb!oxwO=(;Rib*K z%2JEf6VyZ1m(&;3Zna-sS<_fEL9;@0M)OuvqTy+k+Ai8|+RfTk+7H@0TDH!vZKYG` z7VFyTZt7<09J<3gz5bQ1yWXW+t{3U^L91M(@1tiKX6dULw(I*DF6kFRQrS^MvHq#S zr#BgxhPXj#P^RPN0VMWRS@QMvF97!2!I0x%m%3#CYl>UZ? zDLoBOQaZwFZg`YZ%Wy9x({Lk2X1I_-GaO67^gC02gO>2AerC!U{qU6a`i?0R^|ewu z=~XGY`hba(mYMzFzsti4Oqk4jJi(08{ ztbV1itEMYo=v0+(YnFNAox3t`?zMtH!}*vDbqyddh2H z_;#kWB7Z0UA`K=_BAH1P(p1vEWF%QFxf&D+;^c_L(L_96HL)-LI9@y6E&erD3jF5T zP>s29&iQVMgM-22%CGx!Oi8!846yjv!EMDSWhClNT#!q=0z`ja_Pxr>4&tig1 z=GRy);A^CL4`5R7I*bK=sRTX&^W#G?58el}1BKX%cYxIvv%zvfpU?|5tO(uf#pwBUR4dEEvlFCpgW|5jy!Zh1CU*gfj&U(Jet|$jII%sxAC3 znjw?{KeZ1~Cw7U`L|?=ML|n;MQ7g#{(E>?OlrPB^mr44HCDK*mmePFjROuh_K`BM@ zQkpIKKbEcmN{*~;w{3GK_QtktO>EnoaAVunW@2N*4K~_nW6ex!rtR*k_Wyi){?n(L z)m4qEdtW?oE4wHps;S6NwKB2Sf3JXEQapH-EV4pkFnLe*8tRS#0C z)Z>*U;K5x%ovW;>UaqX6UIn*})ynEvs;F1OcW0^6sGhHssi!F!>QTywDo1HnwZjNs zM|ne4Qh89tQ!Y~l6eCoh6)jY^6cn)`Gm2D z(Sf02yoOFn54w>40eFi(G#c$QHJ93#8lilqOs4dq*r2OBIMXBZG5t2(DcvRgDD^T` zKh-XEG5HuAfab~liMt6IMw>P9>+wviW_(WUV$4fciH#=n$X`S`vM+IhcpEKEbch~{ zK8}<`4tW$crc#l*;p5?Jp|atcq0^zu!73pjoq|^bb%S*S_X4;5tpiQ{uTd?_@pp!n z%t!B3-%xMT>-4Pm&h_ZMEYEq*E_ZWJ1@|ZS9oIy6cNg7V>^$IF>a6F|IX^mYIA%C| z!#_7{zXql9evStAl)a$%BzWsx!BLMD|13IHybdhahDEGmx9xV(5#SbcY~_l?z$(79 z?z647_JjVn#>TdOv%az%v~IPGvi7%Bv6i+F7Qgwi<$-y-Wvh7O+(DnO)bp*Ocl&6OcHYyQ^KS(*-d=YS5vC+r72i=-{dU3X0jHZ z2X{5klwWwtR8aVb$%6a#!V4yU;dN62>#$8PO)9*n0^Zvg+vtgHO~5u+VB1GcgTZB4 zXf~S;n3?7W=E`QPxfc}I7n^Ha&X|W;zL_^!xR!gC#umS2x}}Wuv}J(RWZ4dt)z{Vm z(3sq7ZEyQ-U28MiUfD+2Bt?0)oT8xZFm!?(MeE=X{nc~jk zTJTREDn2A$D}ExrEVhfkh`CS}t|-w<+DaONHIpNmFPSdcj6U~q$r1FruSy<8ex2mc;RT1nYUQz6J04EIB55CfOvpA(@9Q!J}2u zMlu`y@_`brq?x1`Ue1rj8Sz2!Pw{MwS)Ih2#aXCg`cTt+AQFhzi+&(G&O=^2TT~=$ zDmo)%fsOV`SQXj3ThL9IClJG_>mPwputxBa-$JmKA44Ve0^iFU!9T=P@;mdM@?zXs z&>$

%#qq8wM(LAIAs;&l8TBJr4Rn0!|tB1@>cBSN3q0hm~TkXYE03Rg?J$S??g` zNJfMaqi;aJxHO{-{TBTetuwt7Y97C+GiXz&bXuCSgSv@QhH9i-rJT#Opfm&??Rk1o zrcc_QHl=2zr=&7Ca@M6*CY32^@^JD%qGGaq;%ee5RH>TApT=Lry2g9QzQzj3;jxLN zlZ+BG$>l_n5D}{o8HkAr(Y?{@s06iw2J`!HgUB#&N_?U2;U%G;=!uLE9ScT-wSy~y z&!CVsFmNp3_ty%n^gr<%{C)k`e0E=FU#_pnEA=h&p7I*KO}u}4K6?7&oFd(9&tCU2 zcO!RGcY*7t>vwcEE4g&&?%j1Ra1L>nb<&;h(Un{bXTRSZggxJW)V{^u+1}b7!}xc) z_)>Ad;_=12;?l+UiVC5be6Xlu(cmJJEvslhP}ey?U@L5sZ6B?5q1a5Z^|StCHCp#u zt(J+_bC$-|xfY4Fm8Hlc#rR@2@3vfn8o(NJcgqlSHA`JHuFGpcLbADlM^Cv8Cplkc0u)g{4!lqc- zn4cGRHNPqxV18dX*8Ht-j@eSU#_TWLZ%!9pGK)>m%_U7Hb6r!?+{L7|j4{=>EHm}D z95Urv?wgKU3QW%}baTK`!K}A-H@CMgFwe1`G@r75Fn_huEdpB|OH12$%Y54*%T?QF zi`OQxRx0Xj9bdH0npgC~>MG*fsup*(%_`n*`?L7FjczYp)WtrfXpj9)k$_(L5vaaInt>~GGdwg}E%GjUI?{qD8NGs|t~9A6c7oN!j`7HOv6ZAV zMgezkVeCyj3Z2Ip@EZ2SODD%Ab|ftcW@<=s4lo48sotra^qbVPbo+GO%%k+lOrwmP zay7GzQkmjK{cr$Phj?=fwFZqvJ3w1VW72)JDfAKa&-70~Vzgx(XWT@!upDy|a|iPc zlg6sfn!?)3`UZSkD|R>bIb7t>TAa@r!O6$S*n+Fz9_LPguHXai2w=`0@J93W{2QpD zGx={&11lpq%wHgI@LvmRqV~5yFje>fx`uQiSJXz>TeM1e09q;*5mi)P+(a}%JV$g^ zd_m+CTSVm%cMg|y5bu*L5Wkn47Bi(E#r35b@i-{5?E)+Ik#vT{E!`}`Mko_x}CA%i+CEFpXE}J3Y$+}7`(h8E>Qi@~~dJ@B==fpLnOT-x{%KalLA>JkN ziiS%56qS>##CPl}x*%4GriyA+iYcz>)qd93ogP)Cw92Kkz;9 zKRhI8#qT1B@e=&=yg&HEc|G}ZsOdgId_R-hkyncA=f30Yu;fp7J|oG^GTE%-qYY&kV?9Wr*}8_-D6DS5JRVy-tlwO-Ln@tkkCD z@nlKFk=GKx673TU53;1gE8Gn|L) zQs*Um3FlLL4d+jL8)w8mz$tM|cmC#B=j`G*>73$t?A+!sJ8yw^Sl}q<;yOElhd9GE z(z)NY-TBn@)ERWeoF(1WTs_=lT`Sy2UALfE?Q`qhXCI~kaq z8=iw+s@Lr8>aE~A=$#Jl#Xo(`(Cyyl>*X)@9Yt?D=-=&c6$qdn**V}2DRS z8w>?s1iOSPhw?(3fh-7zMu!KYlkzBBE>am?l${Y*gc9u@ofN$u{T4M4?TO!sn}i!# zMIUlE`GDktWw9`}E9Q;ScGwHZ1O;|R7#NAn_85Tq`j%t z>0#+i`d>K6wMPH!MrJgn0_7KFAEgiK-v3aSP+LLM`cLQ=)S!Q&od#Yk3(T(VjEW2{ zb050LJh;rwXO3n$m|s~rtd8ubtUK&lKnEW|eMi9Iap!X8aZON->%{HByT-lC(*v=* zoVOX?4pHr%i-w7hK}9YulHt4`kG%Ol zqB)tgvt*R?Ak-J#61uFRw7YDTbieGK)F#tHePx(zhWwQ5hTI{G$ty!$ZjyYS;*9*7 zqFC-zlu}es4pa+!Bo>7^U?^Rr&GOMX{>fWlx z>ba_c>fNgO>Km#Z>JO?5YOm@kXf;37S!$oUmYSkztCnbT)LELb>Pnhf>ROtm>PDKi z>K2-<>Nc9)>UNs_>h_w0>b6)~;(im&9(5hfHgy%vdUZ+7aOxg_^;1<%b)HJ5UatzMCaPYmI;u{q%Bz;EXsQ9KpUN7l z>q?etz4DuKI1~`-D07u;WhdoVg;sf5QKXooxU6WUSf~);`m3C$I4XZ9A1~i2uO{y= zkH`%2+p=G>g|dUPCbB``0_tV=q+g}E(jC$U(r(hI1fw$|-bs>N^xSJp3dAn-;g5>D ziu%s-gBnSGhqu%2Rf{9~9eTxUz!z%{enmXv&s@u#%}fR7qB8vc?CHYv$@HQ0DCmWk zNoS?4sRDF%j)HeKD)k#YMS@9h@)k0uCD06Shc2=d%Hcl~KN6>LnT<=UMC*h+p-fog zMe&>Q+wt}Bjqzdek*IJrjEmwj=v=#Fk7CbIb36_u*JZJRv0<^su@>kDm5xzkY*ZoL zIiL*|mB$%#0(2a;{c&SV3!8CjLAMV28ekp{9PsUr2H6xSkB zMhZyDe~SpuNWrXAky_G7X2UJAJXwvbPBtPNlkIWI!DSpdmRvy2BR7%j$>Zb^^rLQK z&u>W&>A(oWh?S3(jJ1t5i;ay9jjhFJJs&#}`xicGv6wGbDz1)qj<<-pen94eda*1)^A0JF?O#GX8l;DBu(lS{inG3$lt>j6t_)N)~sVroigHu;h2UB7E zzIEYQv>-hi{FgJxKmF;hnR=OB;3^kpUSuj!wq>h`k0P_PW7QL8WjScQW?{E~R;8c#EL({Dt>}SDjyvzmC6+Z{d6S zjRhS9I|L^L9@Lsz3;PQX2(Jo5LZPUmXbd`^58xH0My@wid|3QU91mcgb5TYAQA><}1D{?kh?w zY08PpHp(l?)ykOixw46hty-pPqk4ifpQ*m5YNIAqE7UcBDj%m#sSl~^Yd)xFXxN%F zng*Ie&3KJUyGPRz`Y*ZKpys^RsQsmFtL5vaXzS~CXh-U9Yd7eAYOm?i+8??s9Yx)M{Bsb?QNuv}aYH}-2}5rzUG&EcZS{u?P4s(#x!-0e zr(b8#>X#aL`q_rKZk)lc8({dP>uC5(*T8UASI)3QCo?S6CG{h8HhnwYYkei%RlQKR zNAJ}x)W6ma)}PZh)vwbU^<%V2T?_43ogUvMq}il?V3i+H}e`C(KVTwnO5kOXVUTX-Sh*XaaN@3 zr8}mhDJk&UUsH2ZM^g1Lw#G0f+)XBvOOwx%t&`i6Ja{s^OZ)~8>O^8_;$@;jVs9cC zpOCm0ZBi4kqPgh#P9pk52SZt`8=OR15|yHLiL7W9;QvYyyr`C-MP)=H!Y3jT77>Wh z2yY}Ebw`p>S0sVkMAVD>0X!FpP_Z79pho!wH!3A$Q4L{?mOw?M5>Y2wn`jknM)ZLH z=!j^4;LFDnE2DFWy{PM*kM1TONArkp(Yr(-`i|fcR`jRhL{maS4j{^tbBJc-4t$?0 z#0>PWHXtYXgOo#as18)a`;jSfA*qiYAsfV=lKo>Yaz1ox_QvW1jW8_s6Y50i*cC9Z zf5ckHnelN@{oD{A7{3%>9sdO6LLy!Ow2c7Ts|^y96Jt?5+?n_@@fccH0koS-B&!2? zlY{wiN%CCskK~W!+axbVBpar(@z1tP?MY2dJxlFQMbVKf1Dr&!v?#qiT?aYO;B-EY z2Y%*Ex4^JzP%dvT23qP?Jgrdg;VS_0}ja#|&N zCGaMj(+1P~&}P#o&^FK)(T>r#&~DO?(q7T8(DLbzXnwdUP#6U?3BygxVvw}T3_86Y zLr8DQP|-Uxvgo}TW$6PMRp=uaHRxj)b>Q(*AK0LV^eK!+|1JN!KM~K3!)qh4&Je7d z!zhRTQ#KS4)NnEp(W@~S^itTi0{dWMUlHunf_=ZoXSj#YavGm$8$R1Y`gk}y^rm&B zH=|XjSD-2BQd$f#%?~KL-=qCaJw!W8T}0aqmw=hnhO|CZHLV^sMAcB=V75GsKIwAm zO;jm%QffkZflKWMHfd$bH3|z6kSQ~Wawk&_KF3tbWOOsyWKLuZnOSg5Y5iX>^>Esh zo(1ewhjeb*i0c4Y-cP{3-<>*#e|B!F5%4ZTVCwRdACnh>yjqeRlkAqPn#@i{6W+xA z#J$AI#QH?%!~jG@6%ya#(fE=0V#_T_2{d}vgp=GmuO$~ zWepK)#2mhazGQA>O1OQbPFN91giPT_q4VK&p#`W9wF_%Q3Mh}8LV3Xpp&7y4P@`bS z5F@Awy$KWr_XMs1hxdE1YM^H@<}Vd|ir(s0|D(VV=t zrn#NYp6=JqI_}F(jeD0f<;r!Mk()kub#h*C)pG7~8J&w=Z0A^4(9s?K3XNQ^ki*_~ z$X%x$6xSYy*SXg5)49O$k8`r)u5%bPjr%x`I6FGFJDWRJJL@}goz>AFs^}Q+EaMo4 z9K63%4_<}V(Z#8Dw0Ei;ZLqX-YQX={fpcNRQVRFWJ6a=uZs)Ay=!D$8yR)sMud|0^ zh;xu*jB~tWs&kHGzH_Bxg>#2vi}R%85PDGOoX;J1oIj!Kjzh-~{4)Zr) ztwsgj0dK_&{u^i*D`c9qq7I~t4M()TEj9=F#i>{z_6hztbf8*m#B<|= z<9YG57|XB6sfmJk(4Rk%il=U;#Oc=<EF{u>1EKGSe!N^Q^-%xPJd5NO@B&{g9gZO@W1*Yw&<3= znQoIlpKgflS55CumrAcsE7MET?DX_BnHq`yR`2xJRO|HfRBgl_rP3!5gKPy?Y;nqk zird#z-_&D_XBShI@x7F(6+jA4PT7-vQ}4jjzm+TxE}1yBIvGt)N&ZUqPCiLCN}j>o zw*@$b*-2MoAmWwgK$w(C?oDu$ixQs1@WlH>o5Zz5MQC6M5(^Xl_|U`$D3#uTtJK~& zCy^U>#)rmV$6I2aEfe1sXUAv9ow0uL*Rh7cj~NluP|!0r$NI;fB11eK%Z{zbus;PE zVvpDhvQF$gsfleRljLm7@BPUqWE1i(!_DZBO9V&_+tKw4v9XCwvS$l){O3t8lo$s z?C7*;5ZISrk#^A+ky_~7WJixh1kufrSR^-69GMXL6zLav42{S z2D+2s@JJl%y(6E)Z6nXa_0i+5961-xiX0Bhpd`kMtO_T>3&Q^J)UYi)3Tmwb!tcY~ z!q3BPfgf&yK4~YVP$w1ST|e4-0%jRQ)|L$aC~B+W#H~C4hO*F z@nW2Jh3135F)v(qlL&VT(<6Pt64X_VzyMW_%tse%73Q4n;hf0P z@Yu+u@Z87))Lq|(_W?h6F5-u$4lVL8eivJ$d?X%e7!gIgM#_Lo(l9zV(jB;>vC-q; zmfVi)j=qmvj5;FEqtvK5s*5J0wShV88m&o;gZAKx=mf;2tBAYN6Nm->BBIekLIGXL zT7;48iCA_9F__#&EGDlKhslq`L&Ushj28^(PnM4jAv?yFk&|Pm$gR+)xDoS_-!X1v zVvXRVH9Xb{y86@O7h}8Ov2`Dq1zWsAT#)DNwj76_S{|y-5-t^hvx$c5?#tpbFeWp8r4HCkEsRk+sy%}8@bKpy`2Ni_djH8Ti zjJv4qe`aWyK1MwzhuMc&f;od(pSc;1T4$J}Q2$@REM#tivd%H4oOOd)nf096iuIj2 z01TRGtR!p0C?%s$6j1+SNl>?^G8>}#yO?CY#U>>J=M-(a1>l847<@%(wb zegW%U!1@>PzVqx8taI$ctTXI=tUuU0Str<=SVzFgUMjOWyV;K znI2$gOyF&NVjW?=VC?``dJXduE0=kkHI2EOHHx{8)sMLdoQ`R%=HN}&VfKXzcSlwh zvk6Pitj^*vOS4i8H7m&AvFwaAGoKM;eqr z&n?3KrT835<`HnW^MHQ1$|%FU(Ffqi{s)Mpklnt{L44r)u?nb;~*$%J|=b276g za~`uL)X{q4_!+@Gz?{iE4J6tP<}T(F-20b#hiO8e(9bk8DXb_s))v+&RI}~@yY`9ohUH~hp@|V^m0(NR4cX<{z1hv!Q`kA|HBhoT zj_U_-BKwa1Rh0daE#r9E)i_*EXHFT;cusT9YGCG1pzivJvy)@uT;XIm?>JfDbv5A% zxP!TsQJ?L|J-{8$y~|z8{l(4WrnoP`z;|(*@dUi#ysEtAa4I^%o5A~=w-c^Kw|IR1 z4_;NEue$R~^Jnqf@ptnl@&Dv+<>&Kn@M(gd{Bo!WcNA0*OcitkT5Go8mf)b^m*9zj z2INC|V3NDQ8EK|)G&lmAg!hFvp(s!&CK?i<}`X>4ag_^U_ zf`1K97N5GGR-##=t)V%o?VaEjMl(d`)-2cYv?p}swSVhcY3;h9 zTCRQpG@o~B+v_iC$LgPJSL#jLWBQo(fnK8ft}m~Ppynqr^wL!{jMKF=N(#+Lf_#*X@) z(D@i(%+Zg4PUCpvaQ#%{DE&<1Sp8h%c>R1V3;$cj=@%GB>*pCqK%ZlXe!8*0ev+}5 zevGlReyFjvzOS*7zKgL2G&{=a>ta6@jY7TNn9&K0eqF*)sB;?L>Ao25<1?MrT{P_0 z?KLddEjLWoO#lwMyP>JBuA!_>2VIi1KAQmdW^Y-AhM3uVe_s(eK~)fq)nIa~2a z*<7&-x|f}lALSzDLHT>d82JuGHF;lPIW>w0vhVU$s2O&V4U{X;i}@k_DLX1XAR8+k z2ppsyefaOvo07e780#l(DN#w;l24LX;vJGL;+~Q|IL3A2w@~8PC_XOgBpxT?iK~Ez z6A-Qz-4M2gb`ew599StPw1{2`S_-$|4C^a+Brph;!x^@v;54*1rt+Wg>+)B^>#!aF zDU=;I@?JrCcoQ#+*OOPwRYHyKJ5ZB{z^@qzwY!Sk?;IcJBx>c;Ig2<=Ic+$6j)?P> z{TT?aBhcKLz;4B^120<&6nXw(U1jZMtz?a1^<~w@+(*Y;_%HJCa+We0G5VwWSc~DnjQo&pr|+ddrq7@sffhwBy&QBaX!QE%?x^T@ zXc5{T+9&V;ZvdUS8;q^_=(7)`5nynArIw`Kp|WU)s4nVq>O1OK>J50{9i-L-n@3BX zM5R!BQ|*){)c2I~h$W=d6VRU6jOx+?$_L6A%0o&|%6W8$_Mwuw234oI6h5AfXZliH znYM^W>QP>2Do`F|bf``VDJL@&%AQOpvmsNAnB_-iZsuKPQs!x9WabXK`kGQ*)zC}IkmqQbQi*JW^BiBgtQ1Ae{rDYYm~DGe!Y@SAs{w4?N(bfM%>dQk?W zmOK)@pK+8?K-^BC%%Duge=(DiOPNPmit81)UIR46ddez9g=;BWac>9I_IKgG+(X$y z*^6iP{r|H6zuW&kw+XLpz&h)&?kdVQynhpA2_7xLqgj+?lquNqIP{!`V{iR&uRCQJ zK2r{*38gDOXKQrE8{n9zitkYh>;okwgzsj>_xyyaS2M4GO#d^pDf0*Z;hpF! zF2gY}3IBagrZkQMKK_MJS_XD%EPWrj?s3GytI_$Iklq9w@$__+bg#4^T|4bgDbg=f zq0}F#_sB*sB7R;ER^!-|Jk>JgPU^uU3Mcm^|4B{*PNYq8eo~7ntUFOU`5+O6fANdN zu*AMZ^~A(P8a4BO;#}mMU*elND<)5S)hW?nINlJt`k z(0lw1eL+9!$yW#(Ig5Bo)FxIF;b?nk*K?tD_aa(7x(4;^cF{8tW^@elzEY8;kzc51 z>;~Vn2l6IfcuM$jsA4!5x{b9%2f%&p14N$)+78cx;@}eS@#_W`2E2h9;OZCo2Z4Vo z2e?CF}|XA3MLGuC>Wg2O6$kcxs(?+^~;yOtEJ< zD!_-_RJ_Z6ptz?!r&wZ_6~8WiQ?vp4t)1ZX!!KqR{ZsU}ZDY|&TeqUNHdztd_R01F z80Zbwp|)<;vNna)ZT)7sW<6|KWF2d1XRT(DT1oRS%Omp{%SQ8DOMi1)OF6U35;hf? zADeEOx0}|RN128KlULi!HSvJ!GnuXy-Zrf(+-@3CIN8*wu!BiiSk4qEV3=MPSPRb< zJSkjPaH?=Z!P>%31(OS_7W9M$WCQ3dmMr|0&ntYAA1*kbUs$j^|8>Fg{5u6x^Uo9v z&fj0qIe%k8QBW%XSV6V?D+NvRA7MWq zvA<&MH;MgA3wPz0FFc#ytnjb=eudxiXA}nVHx}{>@(Rlp{9V`*-)Cq6-L$BnwCO-W zYty}gv8McjbtVch-K7gZnA#V{O_K{tnzt8rFyASh0W9MIv%vHWzg5sY+>~wEXzFbF z)3nfHH=VJl&EG9;%{=QIb7Sji^AziM^HHn7^2yrV!n4h?G_#$t%(fL+&e>E}i><3w zU$n~FyXd}kV-ab6R#ekQEuL&^QheGruh?e0UR<&$P&~A#n*CtWWc!bzvvwWao(2?G zbnGi0@Ay%i=P*F?cd)&(^N4+t(`>)sEbH*2rdi8%-Z94&aNKs)btYX4oz2}3oh#fd z*K>Dgm(a7#)zkCMwcAs|{nIlRx-S>qW4%%Ld2drs(7W1G-}lzD*r$g7+%PZSpXcr2 z4|;d|8~O76D|}@FuYD5&YH+@X`H4Ut_&Xv03UI<-23G|np|^oPAx&_9Xas84XM^R! z;owAQgI@}-3`N3kLJcG8@Z!i&=v4d>W<`DB4$-=ijnT!xKR%7f2mvr*J)^sbJ<%^j zepEx2L0x@3v6nngd_$i|6RS(+#O9MbV|U4aV`=DCHU~Fnd29{Fh$pBarsJ*R4HC=a zGZKHr&n7Z(0Bwv~;rv8T)VtOs1Bv@U6a~T6DVI8&9FY0~zK%F`AN4_BDmRs#zLM&S zuFoQ{@XvrH@dXt*dgfBPYNjyV4|CNr%t3h=9p7d4WC)C(26PTvP&63vTR{7A9Jtw= zDK9A3(OvvTVZ-~n40NMf;4+9hoH`eJQd{8sewuoh`jGmS`Wbx>7y6PJYB`#W)|6I; z)*Dg5cv>6UV%*zG8v$PL6xub~0@`EL2L7dOL``59%}qNaNh)SW;#Y5){*~6IZ=}Pi&FQyr3eHQlNUum~ z(?d|5sSoywJar7t6$?_&l08yKQSHr5QlUWd4d{(aU<<4PpLtmFuS9)dy(P(c2}hz= z;;%$C%rb(+%!EDOIq@`JE^#bQNi2UB4+~qGMJo3wgXGLChD76K(X+F zav=$VC??FrXW|Xm`#w_r-0l|lqAm(^~ncBH`Lt5q5iszq>~4L&bdX_B|nip$q+e( zl*KlXHDl+<-m!nsoA#hbEs5Qa)s9*J_Z!5Qz`y+L z^5Ub>Kii4A{ojd(2_i8mQ7L&iF*x}lu`S6(&%9YO1&(9w)P>~4R5AD*B~!mrgP^It zC)G3kDYYprOTS9@NOLk<(rq*Ez&V!UOzV|lP&;_PwJ4$*4T9kltVpgp87p>v=}*8`}leT-2I3*$1Q3Nyo)gNXhi zY8rf0zWM;Gb%eE!(146R-$|gA63e)9}0UNAZtB^EAXy@!OzYup8O{PJsnls?|^zSO_J7XTlmH8Po@d zh(3ZzQC1ul%@H>P?q;1>Bl##EC&`lBl8lpZfM3gz#wDkut)vm)Z(6}IWvfgFRm9=4 z%JS2)d2+ApiM+O4te7t!ptvVLsh}x*iZ+V+$~B5*%2$dPN}*B;zFa@mX60ekTVO&& zs*36^swu$YT~#X|iIyG=F9Sk1b0$^g!z|;1tVTzt*+@-H& zyssZ(wCUHt-}bVxn&F$Vr-70++fXiRm!V_U4a1bIPlhd7VZ-GtiSd0_6{9b!olyY) zk_y@Ljjgh`7zbsaGS1GvV_cv8#&|T_WW1goG(OL!Wqr?4h^#jME&sbe1kVk?Ykjinm*|>RqeNT0 zr%6^exXg+Y<*_YYRyv|cM>9P&o;0J63M#!Pf)nC)L)-TcW^zF58brS6k-4{&`eDSop z5t{GX@|t~Gr@FuPoLa4&qW-9Y=&A&)BUz=a+HZ?d=Q zt%9psqqv7$ewvb$msVbse^QK=uU6>g^%d`AcKJ%#9(hC5iagRN^7oUn-qKz&mXsp9 z1(%))l7Ui#ge`q7z9v~L9wwf?@RDG( zaF{?MlnCw!9`eTurtxKh68wAocf9fZW#InQ;Qhrjb0_n*b9K;Veg^%^S=>BMDd=9l zLpHt`$``+JX0v~?E3!AR^I1)R_wlmYvktQ|U}jxsGFX$DH<@L@NXSPmbT=y7Js2Ap z0!AywGiXHqPQM0b*JR**D$wsiXD63-nAU_gh?d4^@R0hA`a5+m5O@PHM;WO9q8h&! z9EjnVrz;{O3S}ah`?#z_hMSY=iHy^jiKLy#Z*HOQw>mu@eY(a_W09tlsp8Z-#0V#W zCCE)p1IMBh(3Ry<>=Zp^24nj{@-N^CjsZ2eA~`rY7UNr|WGUcGSV=WytnkY)PC=l$Z;S z@=9PFcjD1WAoFh~HXz?RlK6>kUjS$&R;OT9utrwNexD8*D%?^+IYNewlm7`^+gddY6sTzoEm~E4>OG)N|=$ z=@0O6iKj#9Quu85v;^JlmOka6oN*L%)0>ODkDZq!Hgz;bm0u6rS(qqDS!v>)h`@PVFfjB3jy`a=2v`WgCb_$H+2 zY(`Z^UB(c`XvQX}G2Lf80=hE5$Yy4v3(|$Tn7NR7ftknr1=S`2i@|CHCFcpOp{zYX z?f%WW1x05ot0Y?tUFJsYh3qlxKY;E1fNFFa3ON-yLQZc^T^zSVq2IC!$M6McGJoQf z1n;^%w z|88>yHmKX0q0jS6Fh%f1Fi!AZFih}5&{yyPNa*X(_s$d41}?g+V3RqAA~OM5`LODj90%Y{luxnJ%QhJCg*Qd`?ms$g3 zgUJj(y(}Y-{u4~6?a+yBPk%>C(N@te(Hh}s@}UcN46LX=h|t-HXm5c|WF&Vim|DijGFhdyHmT>Ri$><%6ZHVt-!CRZe|8H~evfo=g4bhFm^ z&-m;3`}qrfZ2x-SOFU|*p}=S2iks;f?Y`^Q zxM#axL&s*h%kFC6I_~m2hr06q(;xi~yvttaSKw=QICRdQj$aNbv`^pJM>)3ID>-`E z19p}Dw*5!(a{Cc@iH|K-+p85@i^-xZ#gB`Y6>l!;Sv;tybaADkXi?nus_2#NNYO#t z+@k6K)hQ|#8ElNAu(iO zXV%@8tJbBKBi4zQ&DI>t5^HPAG;1x(NN~DytV&B4E7#K6nl?AFhRyY?ZgVZG&0O7D zXs&Agh2;nCe>c~}Z9Vv+Hny70t>G!$+3Gd-wMNautrW`?uxT2h?a>|56^<;WoJSGzqPozkTN!&zegtN}bbNMfM*Ml~VO$Ta?3DPR#KU+Vv~69-a)R;Oz%u}OIuRA(=||&T%0bSd6}MqO!8W0IBJ{cGR-K_%nG0{ zpCK#fQQuNJQ&pHr1_5Pwj4Fa+S{GU+s6bDqy@2Khi|&V7OAXZPr=h=o0jkq(s8LtM z>@f=7JBJxB8DAM`^iOLrJD@ANgt?P>4Wo<$7<~h)6ss!~|Cg{9vM!>(na_F$cK{N4 z+$Go}!6e_vp2WU^xg#ImJPgh!b|tvqbmb^H(>S%jF7J-sCcUGrg25wRooKX{oESd3)~jmhum&Z4;#!ip|k0OdryM9h|A`!#Q3=Zan&|% zN#1VEGyAwzcn7%Ec?Y>QvDCu-n$RDw!Q08L%G(0BpS9dFyyaXYZ$4MSn+g@oQCtcy z2h8@4aO`OWee%lO=Ug54CYQrK$&GNfatk@R+*h12+-uM(KftNS{T-a`@zA8`$|>U1 z{H0DiW!s3-j3eNB=%kKM|ZNc>`8D)XvKQW z(t_9EWlmr{Vm4y!#2h>hb8$oFIp~}$VE%;D(OLAW7cflFwK$KgZ63o4EbC=DpRtJk z5u^7(x|}|W{)<)_^^G8HI_(y%A!;UcS}WQsDx0<)9Iye@&A`|8Lf1q^y@*=OY|4I0 zBUI?Ah&TR5&0zz&13l1DP$BC30kqbU^dDe!j7?WcS4jI(j?}eOUTQvYR!xAfqM?5M zEO|b;5m>68$!bYW(hrW~mBew3^W&k$SRELXX#CIkz4)T|%6N0|Oz3e%{8{W55Kt#$ z-D6XMS*aiULDFJJ!Ic>c99%VW6mTxpk$gs8lE5)!hq8iMp?bl3p&r5Rp-I7sp|!zPp;N)* zp(nw=Le^kGh#AU+%0thwBm8NnfPc6hSgG5<(H4e2Ko>a%ExMACfzZcY9iADv8a@~) z3_p*E@S8S)mdRw`7!O4jMc+owLoe+YFpVN&LbN$?I64Dk*GYm;d?Q*BB61wuAM6YV@|h91=WenS^> zGW`(!9Nk2B(aT_D8_O66Pq9-BH)|>~id}sH8SHWgZTh4RNLJk!UUk#vBGYu*kr??%sKhb}c^3K9l z><4!qkB|Du|6E?iBF^2xgSIYjB4XXWd=(sGn(>4Daflx`^V3*7uug1_Oadzjx_FqdCZ z(3PJCO65JD$l)*X8A{1}*>=`I_IZ{8*sh;I?;HS@XCUhqQ^%Um{EBgT z7c&75h(CcnTEO^+(SWg+!Onu z=B=PMzziHf&oU1^(_!eWYJlT^i@17qrUvTi?sOER@A>oyjKD@*zNS8`ZKCY(lI$ z6uUfNC!QnEBifz@?qN;RNkmb3{gaqTECxQmHQ`5p@M`p3bZ&Hav~hGO@QW3q&muwK zo9|+*T^-4e^orQSB`{ui!i&Rq!yUux!)h>_ZJ{dAJOYw4^gh%lbQ0svf>0de(i?Eo zj|VfrReuR~2eP_Ca6^z592fiMn9@tgjNH*Z;2eF8 zjw7_Fpbtb%{SZ-sG!f&;a`=}gl0Nb>>eVDU7aY*LvDGnJ{9UYlTo>OQABA4>Mbu2< z@v&e9T}o_A#Nl?{8X4PWV8*{EUnI+>G~jrSMJ3~AiibKz4lXCsr_%v+y_;n^W;Oyt z@FU~NR76ZU7rptvDHe(X5IM}J2DO4l;V zftxl9n2|@o;YpyqFqnCnc^29lB(&UGvyOq)76fCsCA$KY+-4$fy3ekPb2OLJ3;f~( z95vU*8Og26y~v$|nC~9ORR(VhuOpOYxA02wKVdx8f~`E5ALk$8Hy2p=s|01Bw>uho zJf|>r+AwaGfyV9#p+PT3~;E!kE1cPMWrWO2D(ZcsFoH&OJH4^qsQFHmfe?@^ouzx|2)jpCQw zt_aJiz#D0l*@~*lT8dW6wu&5Oj$(pxoMIug+14vpD)uY4EAo_w71!Yh`n;+68Q z;*;`;;+OKJ!m50ya4J74yvi?%pz@m{to*Ks{I~q?{{No)-)nEN&i~eZjQ8ACe8x6j zV_T20&70WvS>yCrj^lO!F0pE5{Q60gK5 zeki#w-YVG&O_Y)18j?C$@% zGmrfedCv}Ds)n#la80|&3IY9jAMwOGV2b;&oXk?JKbcYZV%xUO6Wg|J+jhq7?yBbdOa9+_*0-{1($k)q z>igdN+82hytE#Q)rpllyp-NUNar}RY7vgTvz47Qena8L zk2hXnStXebj!z&a$UcDz-;8~Z9gN+B0d6SWOJnU|71fRnf{WA{9#j)JOVwgZ zzVp(1{*Gp*i?A`l)WvS%OmTsnpW{&{2xube{6F2rx9Ixl=ja;rBCDeBaZh<4UGew$ z$oHR7e|+cm-_gy{Bvu_alV=IfNO|mB)DXKKO%;0>%^G{dRP;x*Ow0xMJW39+2~JcN zd;tD%v#Q3H(amh9pE(s9%llr$``;e(#?HnJvZt{eviMkeCa}$!oAt*-U?zOA&G3fK z!9RLM73rZ%w!%*@LUw8+@UMX&<_qAcAK>dl)axm@N?XZBDhdADMo|skT5o313l-N$ zUHz?irx4({l~5K?wo|rb(l(L&zTD4Pk8RlG^Doi(> zUtLk%3O@QYxabGfXVmZD5NqL{mx6QNP4h2|;RTxW__D=mKEpZJYim%04$*ejuGh}N zDfms_xy8L=k*OIKQNn~#A#<}gOekE(?d3xWk z`o<*MjxrQ9Y=i}T!!Qyjw>1VE$%JK%pA4OiG7#6C#$Cpm#=Fe(l8iHq7Gb-wm~hqD zLimiId4#NhjKWc&jPRI5uw?L6y=k_P$Fy0fZaOV=G(8qZnSS9`92Rz&Y^IB*yr$=- z@}_uGBU9AW#bhIsF*kK!8S@fTU24|0=KZGL<};>I_#jO;KQ=AKaeSTmn`wtR(R9%4 zHk~qyri(bo-7qW6cg-5}Lp&s(!YqDnHsT-m=g>1t)bhQ`{DgY<0nW&`%|2?+6w`Tp z+K!t)n)aEWlMQ*tw90(JG|zn4G|{}(G}ye{)Ro-GCg$O$%I1Gf1*u8Xn=6=<<~(FW zTJXh?gsY}+!XDFIVF~k{QKr>Gd;FU!o4N?uP1OatDHoWr8gAxK<5uB@aSFeqPQo-} zIiWM&@8ylj#?;jJ4#Q^S9m8njRzqXsSVJz>lz<@*)6zfQ@sIVx@Peyh7_CobsHgu9 z=5S1(sGF)kuWO>8uggfL=PzA8-BF!aJ5hHBtznMRwRs&e$5t z>+m0DC@z2+j0HO=#q`)gwYpDUjqK8>EISzu?_k-jgzwix=8I`%$546Ocg+h-*r$V!F|F0Y>9P$O@ zg9m~;s7d|>@5&r}j_ce4X{A&Hq#!5?(sA()wahFr7YOkOyb)KSBW)5G4?3E;A+_5XAn`2l?Q8;d~6hq2mJR!HF>`ES7?&J>1h1tXaj)Xn#yyN*5_cnhcHD*d zNpUCRN5>tG9~!qWen8x=_&#yl;(NwzAuoM1UQC-f{`|iszDL~l_}<*!pZg8r{-fef z#ZQR47(avOS;+IPihCWuh3DVT>zv{BZpLX6UVztrkIR+di7T0)LT{QjzBM}4KIly+ zpfz2R&?kO-!ld}K39I5CCmf9bm2ivG>2rL##9(|ee%^+O#S?lbHc6P4I51%o*=FYx z_a(eZyq6G2Oh`zZBqWweDw5a+uhcPQqODC@lXyPqV&bQyZ;A3`T~eOpB1sLB+awK1 zo|LpIc~{c;26qT^7? zZt~imIgE~|qr9Vlv#+C#^FPOQ`h~;fb$oEFZv)>~?`WSDOL*F2u=v$BL;vMwTe%}~>PXAVaQ~y0a10K+^%xD2? z1tRzy@pXqZTe>domcHV16T?<9b5Ij3hsLoLelbH}2F+uNvpu*fcmd_(i{QOrQt&(Z zd*NUPbmB!q1@O?R&Lp=DbKC)FRHrcEU5<12P7uOVILh2cSNVpn^*0)GKmHJ^unrfV zRN<`r`-gv}!X@c!D~9XgB+-nyaXT=^fBD)QbZB6>M|c?X{FDA}nVD>YZHz;^Ml>diZ*1uD}{%7W-?J!!EC`KNzdBn zmd;D>rEO?N=i=8h1P`#*(iHT4eV7Y0#b2nblmy21Ow{0ykpe^Mr8rHzh?;nZ*Z|H| zQE@n)5$z#ZRb#EpCteAd(BFFla|7>TQe6u)3+zEZx`cjlH0Md@z*m3Gz)gC{z4%xx z_KU2dpUHK(<*(>JEq$NTQ16+H$HyAGY`bi9!JdriKUaEdFEfRWq#m>jnAUeUAL`^~e$`+(fG z)1JZJZJv(a#o)8!J>|T8z-U`}(t^>dy+xVtW%2y-2%guTko%4&!F|s2-hIGx-@Vy$ z!M)sb$UV!m#XXLGda!4Jyi_B3^u_SA6~_EZ7WE#uDSDdNuP z$xr{C1AI5L+vrK>)_GE~{#)D%kI60f82NnaIdp#ynMcp{26A!)w}$%|xUZSVq~fvZ zJvn*4{5)?lp1&NgQ;nX!fxDikmAj>l_ zzVhCDzP8?4zR})pzSZ6-zEj>!zSrndCGRI+X7GWkV7EPeHOVyU?LX|B?|vOA4`KdXSd*q^$d49b!d06Tqan*HR9#>>*k^U0e~lgvdqdJV7muska~(N2mXinZuPUn{;T zQY$klTc8qMqFkhWK!u}I#qjMYtD2@70`Bm~>+v($t!dRm;Xdp~zxR{wE4#*}Zm-Fw zS)u8K_GKB_vA5vf1krC6!R52N_PchKHiPaqIH9DSsVk^E%M?CYw-BDg1(LTDb+f72 z&gkpulS_DDGEqb4$8)%bDTld} zsgil5sf~G|X^43{+M%=bypK#L&ELU%{ie^<#2ylSG?v2V^!SSYV`)r(+|gVg?6$3C zjJcO(hIzPUiFvYRt$7|gq!pF}<_(sUt2XxIdsDI)V${D(d-h##e?e#*2m;#_fiD#`!2GhZ>ylSzZ__qod4CPh~JnGq}iy zdd>N8iJ9XL>iY%clMiPu&>ru+YRm}ok$_~zxy+|)t^c5_NWN5d{eBYqR_KzLCp^>j z(4EmWB=5YGZWipz0Xns=1q#p#y4Uo3S8+hwr*(laywy(DUe)&1?$fqp##lu=5&l?j zZE9RuAZ&l#e{O5dI()>Pk1Kn)nn96 z;ilC_9a~A=PF<4k3aIO-bHYE#MA}wrbrH3RURO(clY(m^=y*lCU$4rfcG90Dqti`5 zvm2*UsDF_i_mfQJA55G7cf?dbx$ZZ&#c`iR^u8%7+$+HmeQ1FtRXTN)YzP&qG=usd zu*Kr)bSS;EsjJfi)mInA-@H6%V-0m5bt5$3ZQ(t22YDHcAK`fXv*xIe;qrS$y^Wvm zi29TI61dDGb(EjjqDfZg&_vZGH5PtLIW+B<2@E9Nek#fKE4j3nOP9Fxf=h{-%Nn`% z1+(1*^wx51eG&?~;et3`TbI95SM5RVSo-y4+Hd@&BHA0QDW9~Zbs{->slbhj=;rI{ z>-Ok+>2B(#>AveWGEX|k`S4m-1&{E~dK*1_QPjMRQS%Pq9Ga&O>-R9fzOAop_^Iy# z7CMFVZi}I&;TkE9-_iD}joUakuaTwj)zIH4H!d{hz=yns@rkiJ+}!ELG{QE{@oUER zXbLBw?A<8jgzHp8_$qW4l&0zQ^4o;ErmI3&F(0+kBBcvbUCe7N4aW z8p!sRg4W@d+SUb@uGZ~%s-3eew?4J(!-M^rHDY;ZO@;0^Km5x|)-2>nl(6-%*0qg> zlR1}siM7^gwtd!>wsY2w%^uQHlOvUO=0ucEO0n8+D!N$XCPZL51z$E z?KN!W?G0=->@92!$(n3o?`mt0H*!~dKU**RKwCfiQ1T~-+lG?kG2A}VHuCQ=g8zrx zMsV9O`ykt3?mK|T^x?7Hd5%szS8JZLiM_h5w!NIKlD&wnggqBY?dffq@sP9FqgI98 zY4zH^^1hx~U)ru&Z^COnK|<3u+d}IS+c+@gUe-SRd@XDht(9zfta)q<>+qZNTR)NE zc++y-dH@`H8Tlil!K6D{T3f4F%2{(*GFnxZu;r)ugXOmQf@L>r_Z-&M-puc6o6A~q zfoscg*Z*XCU_Os0>wl(ctdpIGjew*G4mrW;x)uu({(DlL>p<6vPhded6Qv_b}BJRbHJIG+|9|1)$m4ltB7RxzlIcJ{cR`rY{0jbVMO zXXv6&Zz!gZ(<|5$U+CuQcj?+OZ_B4|p!4f8ac(DTSL!ZldywH#Le~wPw1oDtR;k^{ z=Vl;z9u>5sH9Bow&1-y`cj8Ms0*th}W&wKo&U`M5fRDvg6Vy*t4bA^I9IDt5`^ z@S!*-Z!TX5+Sf%MmKBE^tCH`Py+&EMPu4~@8Fp+7Jh$@6KE^^|ice4n?uzw6ZCM?j zY^GR#c(O5aL|%bZ9>Voz7Jj^4;L4UkuVIP$;7>fmo#qIBnTsQ1BLgEX;fxlE6o?oi zdiehFB#zu;?!7-eIlM63iN2;HolnMa3A{Jcgx`il+}b{b)}z*#NEg)wPp)dAp`l!K zSQ-$N1o*Izg2vFXpa&n%4{$DS;#Ycz&SDMTLDPd7f`fwcV0*l2YSCX54_?HZW;b=* za=g1H;kVZhr=iw(c-4?j;7Pqh$|9|j1UkSF9_tC@PrMU5;p^2DZBb3>kXRNExOb8l?nkk}Q85gEAjBd4-*J@7$HidaB-fu7BY|_GjK?Z@4y||#70Er(!gHsl z|H=3_57{L1MnSfslmHgKi~hE?Hd;moj) zOLCqx3}2xWeo2p;5MCaRQDLW!Tt)HoHe4m*47Y%X-3Rx+36V09C6Pvv?KnQ2g1vJu zvJ_p(u81>oA)<^vjig1_l^-s5g{YMb#60-eV7y9Hljsz|za9tv|C2*g3 z6wAMP%n#JdUwOulu`TG8*W!P-BK8o?^KH^&FQeQ&4YT?XpV6HtqSrGGSjzjG87s#; zpkQnOlY-7M3mg&|QwwLb7;3icOe-w0i_r)_Z32^t*L;d^pyxfz?`0k70kfhd$coI& z98?{xhUa#E+}F0k-)%cmODjhPM{r4RVr?v$3I${I*6WnIqKT#^nI;1}_ocor@2G8~Bei37#z z=&&;2Fy+K+=nnjyEoh;}l3Uq~=|5b=fGm)cS#p%g@;lVQr~Lo=m;1~62b1;MfP9*K zWE`o;%>L>dMbF<96e_=O9DYb`eILA~$!bXDTjq6p2jcxy7r&=m-lg=cL*YU;^Pce( zr=PWZhcoAH>UrrYOnSQDS&OE4qUWBwD{1L9J@erS^mhxMhHe-6w6DNpFSuRso?f`- zxlg->y4SnfF;lPR?&-y!2I-k0JIxn~$I`_CvIak5aobH|&WtXb)98}H2TpOMI6pbQI3GEl!xOmeJmNU--0C>uT3|C7shx*wU4a1Yt9(FinMOGvoP5kwcUAKo!wR8Cv)> zZ!u48@S5JSJMm0?=HDEM;p19}OyTx{Ok~j27Eh9s z@I9~&pX{4rH86@Hc%p6+>qw8p;Zj80C>6%tx|8IS7UG3^L23-9HZGVYxQpc0XY7kH zcEy6=6zxKm(9BTP&{1~LPaqbC@Y7IP@V*}GxJy9_FW_MNXHQNO@r0{Ka=}7tiKpE3 z$W0utUHE5aX1%Nztr{J{dax28!c8 z&>4o>9GnY|k;wCbNs0y+i=xyUZJ9AnC8Kt?e3ksM{H)w3|0vI-kSVGwaxzz}rC7k% z!-_f7ExXaC-X;(4x1yX%DSp3{-$YF z6H$JwV&-^I)lqd*HB|MUD#?YvwuThaOlSg%P)Ai)e}m8DR`sMO8%5tVQ=L`40#D&B zbWaD-51d6Ma)WN}vAP@lr~Y^x4TJAHR_&mJ^5KRQq<4y`7cg&Hj6P|JTBBLY?DBty z=I?b2)pE@|bwoW&Es?kEL2r_*9;yC?qtYjJFIaY+)c5JmuF|EQQdh>8s|2p!dGJ_D z$G!EuT2R%Eey^GO9iC8k@q#){-M5n-a2Yz5Nve0MeySU)mi&yB`C0REjRmf9K-oa` zSy@bVOPNM>KpCVG{G=SCysGS|+@Y+hoXzjA4~}B>lyQoGl=l>R_*!vHac{#F+@)xY z!nly4n?g`jRV2zY<3TI(7kWX8))9Gk=D=0uy-`qA$D=hfI$yu+sO$;O6?;)q&A?-# z6O6>tIF6WQFWIY(FejVO4%Le@;}3TxBfF-TUGx!ZpuOxiGx*mTKgDvmM%be!oFjhX zxO)jT@Bh?QZ6oz@V9OaXlPeO>u74A^)~(@rpxiy!MXH2LgIU|d-jE~o1hvHh^i*@f zrhDS_TOCw78+ddWjP8B#Cw}%fsS0<2LC*=c3-$xmZ%BPu1ck3H_)+pom)TFYNf)X9 zw!s*i3*tN&eQRq}8kN8Rb4$Oe_a2LWs<$ur&EG@cdr}-JZWX)Y_1X~6iL!Xo=SJ;o zrwWu&0Xp!0{TxUmJ`QNbs{x60lO$BQpJDgDK$UwJb?zl{!%qeV1r7vy2DYQ%-GI7x z6}sOg>`3ziHBjeP=3eFDr$( zA zw#1je7hW1;SZn5st;CJ|bVtQW)SoM;Irma)UJ-R<24};EuPl3RQ!x6zVD-~TJYO#j zgL(JIpJOkH0XM<+zvIy-!!0r!xPO)46g)jQvL;>R>xbZ_U?lh@m?flORmzR?N8QkL z<_bGQi$izFX6xRwGUH~ zyhRhB#7VO-6-ztZH)s6qO8H=j%Fe#2(Ocz$F`P5*islI;16mUL@z~}0S3-}zI zSB}f>zz^}GTF))70C&6()8Lhy|JUT7nGBm1Sx^=>AR%Y0Vm%t;dy41y8q1acD2u{; z=&YOr-|I9h(=Yy|QKe=Dst)UGh-$lPqv{z`XgRn~F+Ss6@lsoiir^x+Qlk2;I)g@y zcBzDBw5E$@J1m)}np2vX<{R^EleWFKoOU)!f@9jnaBt6OjpR&}K@ZhkS6a6S{n;6` z1K-FH7s#S0qnGKsg00QfH$inaQvVj^fSer3{BU@hg3FCI6g6xxv_y+J+VGo9awD9F zqQ;MgmN=h{HfBR}Rt?v)e~lkdZbeXVWf9IBD+_O#>-)h;?C8si2&YIBcqa4!Kb!RA`;{HA!=i>3nV8Oq6zOV2l-oc}I2B+dK z{D{l&EKY*q?1MAB32b#4Jl&aaGDevf!^xNoH)9ZX~wxOPj24slq0~6ld}a z&rM&1OK_5Rn~n)fP3wel;70$N`hpuZA^ESAkc#Xk5w6HbbaGeWN9+)Gfw9gr_7nOW z8wd@-zY7_Sf?!O93w_^s&#>3H*D%*O$I#o@3xE6?hU~^{=;gxtH-|un%7E3_X5$`hRt$^yN7Z>|kh#+Vi^W+U2?p+J3r`+G@H+ z+H`p7Cu_r+8{iG=wEOW)oS~_s?Fiqu1RM&3=8oo@dXwg?dZcC=n6h7^mH zuN1xUqNqv^a(X<=-HJN!wQ{k~$jO)bB=4xWfbaQwxm__Hc2RrzeHg-faJrf$56XJT zKgg=Xy~r-#B8#!Re8$V_vaEq@3!l|#IQ93yTfL6#Gfd2zu(b~2*R&EZqe*m3z3Gga zk*iz*mR24=)-RS4og7opGp578q!1lrb$)AYsn-VKt1uHC z?uJ+?a^f1v9^ha4BQ{wUiLH^Pqk1bYdw|Q;Z~h7j`7~J;`9@iJTs~S;1q_qBWsBt& z7&C?Bx8)7wKjnRK1(_|+uGopE*$o`tzso1UtXWHCcScczNmUP=OJ<-**r6z+ys2od z{Dq^vPPq#1*hv(;FO`Fpe&teSTGcV!>t8C{Qqhc6Wu$hitU9Oqmr8C1XWwpBW}G%F zgM@cc+gL|SQ?<3Dl9~vAY_nR4yJv3AZ*?us>RzPe&ek+Xmo*BN=o-y$%>_IsKWp-7 z6=2hOw0*SoSX~F;1-Dpx6Gqt&G`>m{S~+y-(4Cgobs=YEBLCKbN}twU(!J1q*16!& zn^9#IV3JXX^|qUS3JCQ^JRvT?V0x#2$Y=AHKCMA!C`nRiGeZf(K)AYd!KSyPf4u}} z_pM)cA=1R3f(jH84V#$NEjT7sii6FL|R z3w4a?g<||18R66^jW1dEF7os5FrGEcCm(bOoYR)ZK87;J1~_RHHUtf3!#jgRe~y{r z8p9#|Xf$pu4gK^*4R!Pe^6q}IUiO>1ye_!x<}&j?SXHuKTI|Pj^n+ zAFQ>at~@w8C#7t#XFq4QiWp3NucA&*@{< z$ls$fT#xHdNBIv~M)_{EXZ>aW!7yztbK($u48OTips8h1!;7Fw=U9~{@|mq1trCqz zLXj)*&!8TnIxIrI-f z`qPWx9#ee|UoGDmpAJ9lhu((xL#Oez_kQta^B$nr9m|})mUoQD=B?)W>@j!_G2fpA zFQE~dtSoQ?QrsH%6>yB@=mC1UN4UznYr4$t)My@lInR;@z0|eH+1J$_JwaJ#2A9d{ zasG5Xa$a=obFM~?EHqs% z2U7gWW``vukK><|%8ptoZTPu|I3}jdbFAg}a608OK6CFKz7)}s#*xNZ+ELQk*3rT_ z4%}vgV;TC6W6tkjJvyh)S=gBlY@~u~9Qe={=QP(H=Pr_I@4GU%T&^0fbnbz!%I?*! zp6;uzh3;fDcj?Iks0~MCn7gQF6a10~@Hv9+gP#1JH=ed0m3OwMsP`27m0xJ<(t1zg zdH&5i#%m$JqO$LucYx36TZ^9Vwr_~f@7w0f{~-T&{}wW5UckZ8 zau!ykPZ>gAv^}5_Uk6HqarYu8Y&9-Qw>e`)I*xzv8|r}T(gJBLdX589vh+pDin6k4 zupa%y1lU-I$<_H5w1v`!YT$7(oO!`EFvVviy(q$|K{x7zyTOTC3SN8}1#wDPh-9I2 zXb4_8F0zRo`cdQsJFo%|!=gCG@DtQG6{@?cu~KlqyW;gVFLoUqHU$kx zdYMXA0~g4FI6khDO^{uLr|}a8wIENHmBit@y}Yb^GN1nKe7y}1JrO>-S@9F!ERCXt zBA;S}qOM{E7{_rs?q@LAo%mRol^@Wuii*0*)XE;pBFag0>HjgsISS5kmp=Zha<5V( zrPIu=n2VWDIk1jKxQ=#JJu4uiB*gh0@Q3 z3qlC)wOW0Lx$_gWwQtx(KdW*wZ_da3xgcKQMb#cv2@>i`(x;SSwpl}dNl(op;!F0L%H|aFu2r~lj-s3FnT)Q%;n$jm5*5~=g zNBm)`gffM4;AN){eS<%I3ygd}{JN!}8>9IwbYkjK1Kn`}7 zUD_%gk`}Qqj6)UGOB#uXW_M;h&6s~xCCRTSv!CqDfNYYB+17XRY+f;2x+{JpkNv55 zoX_K4W>K4&U;T&9YKgd0oQs3SG<3`pK?cTf%?NTFNOZ+BY!1l#?EgCkanHeE3B$lk zMuN=a*(XjG*NZd3Q09~Wuv|PMt^>8-4hQQX+UPUl8M=1MNp-g|RWLwZW{`FS==i|HHb`Nxlf}$`w!t57=m4 zCb#Y3dM#8eWu3U8@Pawy=X21B)nO@X#0}+rR2DK-VOD|e=*(85qPeen35&!?l3+D; zKk&_saK4`5`LETa#p$M@W+?299iR+v;BJ_3DyXh)tsSMEuib;|`UkB?Yt`k~)r9Re zTDMfUmwfgQI#FlV7oy_r3Xf;Geuw_1{v~y;#!vu1n@)zlc-pKnTru1+xM29_H0Cw7 zzz1eJ+KFRSvfqrKjW$8Rou(WPGrffM!czQYE(m@YUpdi8G$DO+ys4*Yr)iPtiRq*% zX!=OMtui+@7bI)Gxp|{`wE3oaJ-z=G_)*`P@oO!eE&o`ikz2UaGSG6zvWSe&gBFwZ zfu$%~siyd-46|4Q)?sU2VVcR}a}{+N}0fwp{j|wlemUwtDs(@FkwXllW{KXHT@vu?K9c>~i}y zyU~6K|MheB%=R1hT=qxy0`^z-qWH9xvj4P~vnSds+MRf_`|MThB1ecLY_DXG*eh_9 z<#I`T&|btIu;;gX>^bZyWWmPUZT4?=t^GZkx@UH`?XLZo?Xvx??S%clZIAr|nXw0L zi|iY0lkE#^L+s;hUF^NlkF~H>uvdnAk5rj~+er{&-*uT9UvO7@tip(SZ;Y6h<&mpLT(Ob>-yrcG$gh6sI3RfRGp zi=Z}rA_MS%af>h>epx+Zc_Fn?EqpaRF&;3iLle>y-Ev{0$>4`CcFC|sKO2mrv7v-M zwIQf`r@yA#f)=EgzBztC>8b5;(a`PGj?oQ(3r4a79za*{0Ggqls;RB5MK*;V9OI#S z-d_x(H5{%#_{B%nLNX5;t9!EVlMxAcjRecUA~poqwCelyMqtrWv@t#)t4QK>1CtXAu2L~mE&4{J8F(C z0$XUsj*yA3f84P4fdUMN15q*3EuxC#VJ@1$T5_10WF-9S3Z#z7!hiVTi}}}qnm%7B zE9#J6WR4vsUuPUi$hBZJm|-)#Bgb?X39KVn_o_+tB(s!R`p7)!0CS;ny4m<|`-Gl$*j6fRlm=gWv$>K{(ro6{Ll6t=}Q_uYVC4QxUi2pNLE*E?y z;5w)FkA-i~gwt2nch#2xo^#N<2%qGUzN_9&zMbCMz6IW*zG2>szII-56T(){F95%j3DC$Y?O$&cGqbG+q z8{FyKo+93Y=v+#mC#V3QxF+-0MxGAdw!Ch4UVkvz;S=B%&qHVMpJ$hMm**7q>rL-% z&nxd6&u{W{M0}MDJ{y@hdA!Aa75EvOF~jTa9qgOro#|Ue7S2KMQQs}RB0hV+z&jSn z!?F2``U?6R`fB<6&@;{SjU!uVrT>cWi2sxC0hw5F{w#iVpfWj&o$-*E?C*k)#6+|^ z>jU5Y=K^YG(fPsP8wS#gL-62Sfw%4HzqI#^g}m&14avwFM*sGoSVOuf_GC|< z!ydSoS@#2G-YN8WHoP}Wk-gM5=*4RyGpKs4;FaKj;E&*npf+?KPq^>F#`wq$LnXhG zv+Fbn`@7ISa&Vr7axjIj3H!e{*!nznxC5M(kJ#Z{>~N{T)=T5>);7|f8hHxtZo5FY z?!%*SMWQe$vf)cn6}C@TP^c+jXq%%CaRqnc)S3#ctqilC_HYg+;N8BFJ?|1sh(Aio z7@w4EvY)u}$YotnAx@H&#gC=A?5vEmMD&Pm*+!W~ehMdxN3z;v&i@N1X}mm@d<8q= z0TOd=fX06SUGl(N(vuySO|ea0N^wG7k37Lnir4ZX=su_8q`y+31({E)IEe?rO+|64 z#430KHl)UGgWp#-W?loyBOI;B#^?PXY zMA1cgT+vN=iY&v^ik`|dieAdI9A~(ETG5^B|K;{h+^-$?Z^`4DD7R6+Z%|a@`O1NN z7FEv1i+7SDqjIFeLMEb8*#+HdOJ;6$&`g#m<)@J195aP|U&>m_+b`3|NN^YAkqEceJ-$luAz$gj%MjhaW*#>^U6J;4?9c1BHIXGNtasT(=>HipR%6_IGvq46?k)2qPS~gAWbu@)q_EvNm z)7?SQ(ad@qlO>%y8YLm^E$)8Dsbv>N#z%VN$WVp(a5_Ag95A?Uhd1$WC{C_5!;Q&d z%N72OuKp}jxdowJp-yyTg+tBpCCnFk91O#re@Y#;3r)=*Z&xl;+&HU(4fXUwOz2hxcnn5WeWTxCsLjaN?pz-c~TbNy)o z9mrNJ+Un`!9fxmBb59o56Un23xADn+%yZVg1UAag0`YjR-Nheg zpL-{L^DNh3cW+m7JZ(z53!;NEyP__)^SkS{^RDZH^N4GkbB$}hbE<2Yv!APjv!$yR zJ`Y8l`Qb05cB!3mmm7Y@7sp5V84uuRT)?a0fOE5BlXID4DIVK1oMRnh@RuLx?BVF< zZ0~3XKcguaNnK}6M>S_6Q{>ndv)-K=u1k6MCwEsgD6`zTJqOT{yx?~naj)>?^_=xI^L+J8Kqs;r z9?f&sa+!A~IcA5woxJaG-_ZEZc}w^byj^{ne2aXIedm3XeF?s!zKs4Kz6SUujrTY5 z@ApsjeP_R~3CMR$VpRS{*n-UDd%}y@h}QiTsDT+*<2uY)CMnzCeYy}wpR39oDnDp>LDeD< zfomWEe$_2{bqR#Lkb0TAi~72HDf67G>U?NE+QH|Z3o3qwE+7sBG_Cd{d07VSaBW$% zwLS1WUZM@)+?7-JL)!>{*3r6hy3M+O@%ovobLtM`NAZfJr7)fpIhi%q(I3F;@`-+t z-mO1^=gc#G8G}dP){xpT1_XSyp@rduVVL0=9fHHKpPA!rxY^$f)yZ$^XiP`iQZe}1 z^^7}=U5)3Bqw%#_X#9nXa?p6vXcq1obKnV7Qb;k@LZ{FQ+_@*%_6Tt8X+kCZDC-OB zgx10?p&Oo;1HimT;dwblcp%Kd^Kvm5^(t`h^>_(w!H0P#*!LbdzWW8Ya1adq(ElAC zzISo`pW73;-*4{!jmLceQGX>Y5grM1h1&>voV6T?hq2z?D{4UG&EdW!dWD_zuk^qc9xrs_}O zo4FcqxG_v5+A#kuqyMZ+qra&0;v4czH;&10Tis%G6a#cNT|GF+d35)*a@{WQ@M+q! zXz5omUml8ALnCd9Ca?AmJpS$Y|4gDcY)f8OaXe8CIF$TQXVqL(i>L;kq9EE2XJsb( zhA!$5S!FL(R`nrOJdQxunb2&&8)y``R1@m#{HhyZI-AJ1AH#XtT)CJjPamZU2G1u& zCiYREV!7fm+>?EZdLUsrNx+Meg!dMH@(IrSr8o}^l<$?-XC{~z28xm@;EODi{E|#! zcYBR4`y^>6E6`>SXG+x^=Z4}qgWF_hag|*Mzj7iB%Pz1ltHH?3O--U@$4i9s_#~=| zos7oew(t;+z;O~e*OJ6Jg-Kc;YMUle9UNvi4qP81y69~teaEPLHt}g*0Lx%3>EFF0 ziz2Pr3u{IC!a!(CRbMxfHc~dCi{y(0!xyq$% z9=nL=SjKa$W;(Ee=ibI^?B%tN@|tIP?Q7w$;RoSlE(N)y!zDj0?=Cky*-~VM)?yED z4a254Q--l{y%xZv*hIGZvB=!Wt;l-jmWQeKu1B;uF=d7MSq5yQ30+NZFs>=2p|54u zcpQJ5qP z!cRKLdiYE+9~AJ2A}f>Tn#!=EAMB>4#nm5Kczx-i8Ju0vMEl{qd*(it2(eU zPRDyMsz-0 z6VvG{>&xpq;cq*hiQ!r@s!r=K>tEo}<U%%xXfF4#>QglwiA_{1C(%A%2| zVLC4~LJQNzbX(|ZdO%VYKPBFwyi@n1+^OHvVM`;D0RUSjOcQCRSe48;+M8 z&o~|lEAf$7Zn}Yyc8sA+00 zlrz;53YkjdIgt-eMLOQ2mRY3V7>Dn~8)lBT@#Q!r9D-l54quMh_@oUp4iGvT+c0me zE)<1lk=~eEkiln(GyF0>Has+*GMvDRc%5;&VJh>+UdD!o2F9YK|E0nM#IN_jw|Hqd zr$3Ev<{HB!{Oh{t+Zn3p%NnvU(~ao7`uFfI≷6|I;ndkJ9zgx6swl7vZmK)CF|E zv`=-{wEJ}%wKH^Mw4Lz4ETt<1XGe{e@{{JQ_Oxb+cB!V9wy&n5wwlJOO^3_0Lw#0r zOT7^9ihngD&|B4qOPCGLp-;7+DfcAxW_TAP@vW|}a;ma|>${c9@jdUZTCXgj8p2s# zQ~6YxR=HJ~pctyW1m9vA^+8X?M@3ouu?-}Le4%9=Wp>3m!Har&ug^j1yrW6L7n zCQIbAcve=AB&kev4JomlvTN`uR?)Zh#Y3hNpJq$!pV)WYY);c3Ea3CrHQFOul662A zRj@`rh4HY3if1SoceP0Ih?N!QD|y35S>>j)Qnf;To(JxC06y1Ul;s;jmqUZWT5G^W zO~b?{KA1CfiQZs2jPJfMzAMA{P8IBfcB3Y<%xu9mQdk-ahFuR7I|m*aA#sF6I)!u? zZNq%<>fZDTHN-m!4o+6Q!Eg+Main$H7Kdhf6Pt?0k! zqW>O-{+q-NW>Fpdg#$JG>G7&n2JC(Z`r-F@Zr-JjI0Y`U)4!MAezk8Vef~)QaJ+51 z``Y?jP;1uoRrZ(0gCievu8h>5W}nrs^lAN)H|leE{l4Gc6flE0JaNDIo_as{9(dpQ zZhK$&u6v*Qu6Q5$E_omLE_m<%-*J)eFL@t=wLJFS@ILe1@xEgI`p);<``P!_`xC#) zM7-$SK9@J>3wafOwa@6c`qGdgpUwY|uYkXduZ+JIeNRigEW7(U@m~A;XMjzt^3C<{ z_O0=s^X);`e%Alpci%7iKKf05Cpu|WpdwklE&PQ71N_wkGw|!(h@Zk~{|xxZ>jKIC zV*x$>qPI$0Qf-|{UEFfJI z>q$Sw-tYux;l#Hcw%|4Jt8da8Ne)*z2mN0SGNikM)XxaEzz=0CT+L0OUpK)o67jva zhZ-{X7z8_TNoWq-<-;KJuW%@chSGCRRiNYS430LH{cjuG<(s5_$AMlL@Lwq$=^kkY zIzODVa~0V9X=?Geuxi4Q#?h>BeX5~v=+6A+Pg3{Jr~r0XA{G~IM=ISUCOq4i%-=>a zk%&jEMRtH)>;B&)y>T)FmB&ADk?P65{$<0=B;98lc*SLQtq-zUaASAkGJJ#7zHjoz zIKuU%f1e?*NUC2u`uUNHr}CwW1o?g#F}HA1`l2YQ2!c(dQg&zdHMrE?HkTRyMqO_@+Ds!s3DogVDuc?}>Y@u4D?55hF9D=|3 zB-Lrf;0pB;^`i9)zfcw?QtE(Q5tD{O_miI$dP4!7tiRUfH z^OscJfq#8Pm0xvQl@oq)Ce=1_ivLp?nFK0HOAIPU@P2ypzS^liz*l~%tf;!FET}rG z%mioIsM`FOw;YE<&nq1CZYX;*18s}nZ(Zd)Wm&Gt%PcUBvVl^mER7FXR^?}fPWeFL zf?xC&2f)<5DHSvv1i2LLT9A`4Sf!cBgK34-^ zq_=>ZUY_Y~Hn~@(rWQ$LU3>~gbs8LH1DMq`c%r?T4K@V9DoT=3Dptw>6P@>e)i*u3 zR1;Js7b%3wSR+=Ze&c<;2h5t-iF+@i(q1~EI3S-;2AuM zHwEL-aXpa+1vV1LFq!YW;iYT}j}h4-3Qv zhLU46B=DX8zw-STuK&XAU%1~_Qk1?2rgHl%?ze#ZFAF%~Ui;uZ2SEVjCH6tau2wk;`0q!aVdl z4oyB#TD|1?JDV&a60nSYeR=9!S2!HtGTbM|mmtIJ(;4OH_F0_yXP>3C_0ougA z_@T^U*V_$;;m<7hH=Z(D(As}M85*$e4Z=xg2@}a6HU;*;<8n-(}|c@l5sY%=s&^ z9`s_pSj5*;eEq~%jrtS(0u5Q^Md0zZCUJG5`YyV32Vc`rBUR9(MyFq1GXswFZq0Pf zeJb)4%{`4xo2)6RwKBgfp&gA6z<~9)WZ^HtTrB{;+waqZkaKNyV8h(@EGMvBL z)bfw{?iJj>4~FZ8Z-%?@upi++_lz8nm&{II!wYp#JPys3-R9l&XCIoY~nK^r>jrt7NH_v9g^ThDJhiQoNWtq61dJuU#3_`7V? zw$aW2W9q3*r>(6GfH%B`jd==I<|@r?%}D;DElA%jrs;{FTTK$Va*#_OQ}0xNphr6g z+VCG<1|!sI)Xm@|7lLo1BVYXo9o=PBC%U_`uwHB=10}MT+*E4VN4_Y>D$nDJvPxMH zUa1O3%}4U_PANi)CCml-Dh{DInxn`-0-jr5o{T%2Vms&b1lT&Q;O`ZZkCdy~#XrjO z$WO{5WUjtq{XfJmFbBM$D_p*Evb!;>Y%7}0@np%i1a&AB8~!)>_H#5>>`XKap8hgA z0DW|ASVh^GnMg2tpGQ(f53=I_>9l)LJ68cq$N*~S4Ht<#3F{*V!rwsvE|I~rHe8+h zCTqAU{&5A-OlrfYLf@I?T*h^N1GxG)RJ3hSc9kRr$O^)q!qo5qj!XMN+2>PH^$V7S zCzU3cpL8}I6T~=lE_cD%_fd~6Km|QWYC=s`QYs;(mr|pj6vdx#t{$+;9mid1J#3B{ zV59?B16zp|P#9;&@m!12B?Zs=SAqDz<-ki^c(1a?9zvzE9zDyvKvUE$6#~5jdC91= z2WnGumjQ##8+c27eH*UfDKu|8sj*l3zxik4aX-p`-`^YDz8z?71902Q{%vHnuB9$r z29`UI8g&{o$_ajn+S}n9LS5dE8Cx&1CH}>$y)(XG9mq;)OP$`@f7;j5f85ssAD|Za z;A>JGgB-_u0;UxAB#h} zZu&N%)!XfV#$4~6@0|au?*_WiM}Cj*Ej_|dzsB$KTm2D#COo9`Fz!vdT-~^s#ckw#@gx8e^O}S3WC1#Z>h$W@w%uM>C?Vciz zVqUm_-e{|ITRbg&79U9g@dwj!^aRMYP+mLIc8%&=lCJE8(o}VaK|Fy8j9L)^~O-KeI-CiKeUvc-{#9&4^Co(>E7I_cA_z>!`lAqgp(`=kWwitmmW2(d#&@ z-X&x533L0G(eyCnbD)VX0E$%-y+8##$!nmKt{-zpo8ooQIu?j_gs=N=Op5koX4aQu zAjc5CAI9|~xP27&8%>VoXdX8@mdp{)bw9c73-|fJeP8jIr#$vP&vAq2y2x{$;<*p; z8oPL{O}yr+XvNq<-ouP&!Pq#Q|A#~~#Coz{cZh0YO`;)wQYTpDPcn$!vZg#lKX(;X zz=>!evWwr$T6*&N(MOSqCts;BF)gu3q_%#bBs}W&EVb}*3Yr9CsjIxsU!Fc-`Qr%fQ4 zObXTs{R$QhJrAZM?L`(k2&-*9s@1u{^VCK==nFabxKsuIy@D{gGLQqUMFZ}WlrY

zkUHzFQwnvjzU&>47hLtlvB&L^sQL8-_C0J|;aM|9& zLVNJnM{W=`Y6rMzb>Xpl%8O?Lg{f0>Qlq9rmn(oR z$jPG(1Ui5%Gz$C(R0+Hz!|56RVt2{myc#gTv5xqU2HgIAf#3e^fe-$TeCGcP-0?39 zTw*Oa>7Nrg=%0@6cQXCkveQIEse->-b{J>cM(!fOWbEmQ9%;EJH`S%A_`j5l^ zxDeRxzZp2-f5dxu6S(aE9=PjI3B2?N17G|aG1+eygZ}KIj-NMeprV*3&_FC5=pfd{ zW4#R=g}#9W;`qP@)}=$@mcUi?$uGovf!}BzL!vKWl>{-5lv}JQRT7&^t;N20P)wEP zivLO5#Us)s@h;vP-&m6(>^JFxC8QF;=28>-jsC%<(wyKCX$QF(*Mc7DOE4|kowaLdXK+gBUT`b!0M|q5LO)?c>cYL)6&Ht^vMY`tzith=q8D*C{2uPg`Z7OU zEOLq->RWg+*}l8rUp^wsC4j0qJE(pGJ}o1od6{;%VGWoQ^}_kh#;3I*&L(5v^6Wq* z_!PEpl(db4vJA`_8nf$9gDZMc_CuB+OGTGhlPY1jd_HMHmtmRu(7hB=lu&d>;j&h- zlDWnWg$9j4Wo14R5?~0R4g5|uk_k`i7OHHj`BW-bVeLw)lW=Q)tNNj}+JZ;Z8}!9i z%@K87^jK4nA5EwlkT3j8Z#WGb-Eqe!DI^U&}nt=n9*6u<*BV- zs~fMsr#p;3><3vs=?xwAjSTbkQ^7Qk8{+l94C&EV)G;(PjxtQd!|MVZLBM7gRT2)v#nQc8?CQx$E``WyH>gV zlQp&7ZOunsWqJID8rTciI@l}O`r8}9&+7y~Z=h|ZeY|apeXi{QJib%pTV8=Tch`Of zPTe)zTex&z@U8!4|6ohBf5*E%-sZ78Y(Bf!7O(><+QS@Cn+V4*U{}E3Q^Lz}rzgO8}OEwXv7V{PB~Jv_EHv|WI+ zw+Aoy6;_pPvNh4#&-%>T(t6ff*}55?<1DMi+TR+0_xKs^-fc@d>mf_XvdZ$wGQo1y z(#^8NQr9xaQpnQJVuLa1Hy6Us&SbenF3L9am9xx8%ze#E%?-^%$w+C21yeD6r!D3e zCWq;i>5=I_(?Qc%(?WEd156c6^-Y;f`SG^afb;)^!+9O|tZmE>rjj?utoT~l3E z`i>mB)Odmiw7+l%zoWg#jAkq9u31dC`_Uyf(U!+WJPQtGa&1)e3;g{)UE~q|t-=#- zf@U(#E__oAIEwnk=%YKx~-b( z3@B*$> zs8#V_b4!)lnTO{ZH<2hm1tA@sho=#>fRm5p?F zwe8R=d+NTS4@%mJIyHKsjpSc?T8i@Umn_BpGJ#A(Gu>-lS9Hh0IzOo_LBEhJ!vQ^d?~! zJvV3cd4;>^4DZRm{^4S3gx>6b+4C|5EJ=2-7P>=gSmS+25<(k?CHPrbOSi#ZK_i|; zhqxzXCRtcebPDCgm{5oPbSp6vJi>xBdQ=c6GGU)5wh>qHD%>WHpbc&sUh5>h78~f_ z*&}|yZ}G2q72m}}_U&((8U0|QD2tg4F)-5QZ0w#0LWH^hH*e3G*cupH@ z7{R|oxzr%TV3d;nT)!8$+LhaDZ)go&zbWspx`vvD>fCEN+E0rb^5YW!+mMN@WC{}L z27}5F5+%_oe&e@!L6Y(otiRLfFnh!;aL$&{?K_3u&O!VII*KjDx?**_2Sw-}$|%}M z=lS_texwEE7JuKvxKdX0H=n}nwYSide!coa2}q4;`Ql&~8plWI99Nm!?&LX~3!h_v zzAm~zJ}4R{eZ2mM&Vvg5kXOzD5+@6JmnEWW*P~M+53emf@2`*a#Gc_@@P{Qb6kWLi zuk+k+F*Mq9bjmKrWs`{GK7l!NdKyCA>J<14f1nDShg-5toqz(C7A5SrDz*ANN^z3v zJlen#RT*d)7Mdj9qNDAH=`pSpl#bDH9zA3ARS`C9UT> z;iZj^oMh!Qhm=UW$ggk#j=v-5ymuituMg*i?i}RU`X)X0tH?I>VH#2vKc|_N-&d^E zc9Ccp8@x>ZV=2mRk6?136e~LeM^!KAt>4KSe@s9hsE>M^2?ChQKZXX<2L2@+?e-uQ zSjK;iBihx_AqK#!sD^f$8ivHbOoT2dYTt6@3CXRUN;PFXEj-N>x15Jn_n&5-C-O*n zx7spKi`$>vE*`&$sk_$&8A_Zjzb z_XhVm_Z0UucMo@ecO7?gcR_arcM5kdcYv;=ljW=HUWnI*yOIxG08ogqmp|&N0MtCnLLnf z7(b8Z^oh=K$` zmDO|0RmSt#)z}ku_3@-~Po?i~t*5s882qXSaIAiLR=EZ5QFm7FLw9*^vb!bh=^^l> z7kCrsB<$+B=AGpE1Sei4o%Ljt-gwGNVNXjbgLjxz-n$sj_wWZUp{|N);@EXSM2poXI8M8cgjV7 zYUUed{1N7y>CjtB(dXEVdB`BoirqW-a8x0i=t(V(tDToEOJ# zIWh?KBXN-qWFQkGxk;uK$JJXAzfUcA1x?@=w8it&EpiWD<4Y3AU)U!($s+sFWYp2Q zOsW>4yewsZwGtg+Z6teieIyV1LV@U}NRjB~Nb%^FNGY~5Y-RbmEa#PF!c{i9E>b$W z8W+*>NHMgC!qNGW{LxwPBrMKTVT*F4C~?1c|+a8??Mg2w;^F24`o5UGK3d~T%j?r`q8`4SGR`B z!@SRl+8T#~5ein~S;_%<(iA#|A9Q2z5zpZ9;E>>&VB6qC-1pshcK_(D`Jf`&gI^)r z--R@JG;o~R*=i<2<4{~X!4|3(s1V4(mmy#c{A7-F8+PPAQg4e$a|~s#+=4_$8D>!F zNp(cY#eQHec8Ph|R$np->oeFv=V93GAj7?wJ@pu0Na^l-r!*wrQQEhi%)wm6iVomY zTB5O*SMJc6a9lAco0VjFA$;_)Xr;Z-K$|PG7!zkyl95==U8?_M@lVfqXktHc^`lCYsw4xJ>0jTl2S?GE2b30b5YwjSm}l~ zJdqB@JDEL?CeXu-z4c$WJ zm@oDY9mb9GCUi9H8GcMp8mXk)9^wa)n$Q&TOSVgXkAVHcHId4sM|R1f65$DFSOAY7pCjG2nY2G1?bj-k-TFG zA&uA(zw>Nika$>FgD>kkN1tAfL^B%Nh&2sU#6k3!tuj0kFL6BkizD24V=Y5j;{Zb^ z<5I&6ykEQV*gl}AUNPo|dtVn0b)vD8X_0ZV>5y@=>51`*NilxGud6qgG-Wrpfafy| zZg7%my!n7>wfU~;80?FOW|5AhY-Z6?(VWH7ir%Eb=0=t|=3ewDjfcCm#PZO*!}8sH z#u7C@u%xhjw&cd6R~|k`LrWTK7j&5+c;lv7YFL+AnpwA4I^)$#v|fZmbl)=dKQG=7 z%L=P(*#N(KyVYdfXHCIsK7;i%U4ZAU`K?#rGhMfqw%(#g^^UbF>--wld#vv7S?ltB zeR$UmtT)-N@%<&btIl!yNoy_ZVQYeQueFMGtF^p!y|t8enY9S1&3x7=)@;^MXakAd zPZ#d51@~K<`!8qx3D5tnB@;eCyY-4CW;t$gT6S38;;+7KnS*D0jAa|%?Io5rXb81v z(<*6ciI=*n#bGG`KPi>vhgn5Ozz_2|yx}`=Hzwf@AB&T^yLq6wfw={|l=9|G5bE`2 z10L$%xFsK&PB3L%Z(5CWdIBEmu6U?xF!#)3N^P>xPvFFH`OtXLco-7>a(LpS;EK0} znN`79*qF%}kBeF|d@wwPBXXQ>yHzmg$1vAyhkv@FArB5u6Dyrxta0v$7sb7_;w})U z;-T&?Hlg>fn3zlai`SDP{2*O>Q#dK?rb~4mx0P4j-xZLH(Ee*X7N?=%oHX&p9wa!MSG1o=wZ!I%|^0F z3&=f=*PLRCzlWspdQ$X@@XbsmOFu%BS<_b&$Ba}9McPL$=_m94x3HTY@qWEQI{7Rf zrz7grn%&G#H>=HbzUnng@IK9_rD7%w!O3W&6h6w*GAW*!pr> zU(V~xW%_a1{#<7O*B#7l4CA&&ahv0~?aAE7Ozvww_qmk&UZXC_{Z-Y4dwAo)cjD-!!f){qk$r6Cl8hpMK&MAwz9Nk)P_gZnzdeUyjP<&*|l>tCCF{n z)b7)?)?U!`(>~TrU@e%0t9Tn)$ti6P?S1r(FWN?UlX{?GjMe7UC21?@c4?cU)AZ53 z)K1oUwX5(}9n|H3^j48pN({XpAP?x>Cp{9v7-GGw;Ccd@M8cRbokq za4paehO_QoMwjm~u?QUr4N<=m4Qb%Rm8P4$H65a(3_o}si*&2y#J`$AcB7jyBkqO@ z#*M}f^vX}*9kJdR;kyiY0m{NIYHi9&exnL*g3jbOCh?xyNYBH0lg<3zlpkJPZQgS| z%@xd3aU%a?9>cqHC5qp1v)}v_zP=alaU6u9f|lNvx|V5{UgRaFp%!i=PjQB<#dC|r zDp_*UGgsM~2W7FkbqH+zxz-WZ&DMo<=IyXPv0kwLYkgtW+1%FjHUmVr%(hy#61EQL zltXNtZ8OP)ueHs#9k8t<8-5T*+!fouwrBJ_|Fk*mAv&2Y_G0#o_UiTm_Gb3-_HHPj zL+q{XlkGk23++Sg>qw99CO3ZEzQRt%*?!-?$NtKG%>LDW-tHn%9^@3QR8?7e4;iK`k;h1rQVXkoqpQ$wr#mIJx#$Vz~ z!x?c8%%%y3PIN;Q6|)(%ysjS$SK#9>6Nk}3o*4yu=^$9`_ zeM&*nebQgn9ndG~ChEK3nJx<7M2${(Uwa%c$aGyI#|#zmOA-h?Z zjAlBGm;KrW%}Moq&1~|dZPa-*dDT93NOeVhOO>Qvqw1{guPUUj$gzt|mMt^gd^Of<$74YBpr6rdv`qy(jI^#dT-9zS` z7s+29^c8~IlOCd|=$np*VU+J9v&s8PC+3OGeET@AU9VJPo>_wVV}9nB+3-T7#}kpl zSD&>>RYmZXQdGVIN(i5a5C4QmNr5NCOuw^U`9X&BJARL^iiF3)#r7}T4}Sh8e^b85 zKgjw0i-$Rx&BON!rv*7L#$|NA-?GW)k{v!tPVEcGnS3!hr%%tZwwd*QJdY_IkL_=z zsV^VijN#XL;V&J?>#rZE;_n=2PJYR8!T*R1?*Q zRAbe9RU_!p7_8o;>IXBTyLy?by?Oz(#2Ko3>hWYdhe1#5N7HU+)!*uts#NN_Jif}R z5ImUQuqECyZN95AtIm?!-lO^*Tg~q~OLZqUTy;9uMYW6HeI-r4Gh*3PLt|D|=a|B@ z`W<=uYh>K^#%!>P{LwM7k9bRNMH6C&XbfHxwc;glMf*UAY|Q?s6wFH$7t$N8qTkSE zZu6||M{h~uIUGR(tTmkpohM=qi#{S!XMt6~wf@V+>a6QBoV3aiL< zyv5&fCbXTlj)|~}{Gyfi6A^&8b5f8#AUtZrCIO)rM zkH~OrSL)$&$mwg${v@aJN(sqFl>6u$TgX0)mJ^glauy}09FoIiDIT(J-ww}Wg4A1X z0q3Cree-%LBE9jxl#ZZF%twRh0W+eqx1p34K84p~mhO6fc(>EVHw6}aN6&O`IZqF7 z8v6ERcP_L{(YxRM!!zG~)6?I*+fyH|d;#}hkJa7Wlk6((x$ny8Ip|V*mb$*WN4jpi z+qn+9E0YD!>6+j+yLyuQZs>mDEag7$%;MhRG`UfgVe!8slYP_G$a&ONp8R!g=K?tU zV`1U-ak-OQ!^5lPx{s!NAvw3}U~*d5=47*Ld2$48*o9X7**PTnxwCijZFq4Poh_4( zI~ykNb=FMY>a3Ey&RH&bnX_c_0%uV;as`v8I`bt@fG;=3nLBxeGZ(sZ?&QJzJjj_B z9l8KpVYcG@Tn25sV)AfjLh>kQJ@oFT$&;Mz(5AbiO%I?Cc2x38=d|RF&PB;P;Pf4I z?n*xGJdu3Wc|G}o^Lg?cbn+k0K(gXAJGFEH#F0(ULJqxiEsI)}LS zIj7Mxy2AC&xeI;%oGZ@t*p<)q!$y=B`}!J^3^2LFGEZCja%j~ zaxxWz|1jmbh1XMtX_W~@r&h=S>7#IH74N`{yc2)%j!Y50A1cQ?v}-sslcXACvihUw zEFn#BGW_R#U6-uZAol3X(0)&o1AWilTpzuNru&VYj5#_GCSDS*x}*5UpW~qm;vP>Q zdlxMa@1{LThjDnv*Ww^QP43|p?LdLp)mUm6A0^=9HBn`wU%ZNHHcr{CBq%PZR?-1* z1h)1AI3K@Nxzz^dXMe-WE6+r&1v$?F>UI2kNWC6C=opOvcgO&LX8jjbr(yk;51vPP zO=(R7@-iLx8lV}(igmJP9$uT(5a4#OmOaV}?-DD#dzufdX`PxcnlS$wv;o=?HO#C{ zIb?dIB`ramc^1R4w~;65&{eTbrU zO_N1?3JS?SXe67Uk}M^?GE?)JR`sW>wXd@RKfwxoC%^A1e(%{#AV+D8_?3O?7ElDM zv%)Q+InPRXH!I_n9HmYr(=t$f7joEfbq%P4h2e*#R}WX~)E(8ykc*zt?sQ(2mQN+M zdXefo$?1E1G9IP(ek}~LDXKK8-l|xvq3SEo-0fI8j%;Jl2|hy#xdBmm4{4~yu+xXb z4Q@kMWko(+vceSB$M%r@T^zj)-{4PUOcI~^!(k@3hL>D{1a+3^GrXFo@ooMCC3yyv zwElduG{Mzb8ht+#y1tHof8r{YMAEPjY^BUWCwl$cfC`$92aoh;j+~#Or``xG;HOC(Ne|(D z*aLcA8(L=?pyXG>Ygn373o_@)9_SiKPX|W|lzlUPY8~&|s6T=CaAndV6(Hr8<%q5n zN8Kfu5*3H!Q!Mb2?Zbcn7w7wufzKQje#JHS!(WM%O*JSeH3JGqi6MW}fSP+X@Hy$= zI6e)G{XhGKe1WNfl03R9Jf`|Qw$_1zJic>q{cZ=Q1YQOf2Y!%0@pGyncqx!J_#}{* zwx;sI5DiXtoR!)6Yn0)y(lEFlZs1{N$2WsZAZhLn`bpn8LhpjP`8!q#rGy()7{6vc zvM9Y^2TemK-W2-C9wtVgaRxsB%km7h3RmVC>p~WN3eV#fGUwOBK2|hoaNd>0W7>}A zdwgUp{?dPF6uHiW6XSy@@(;xu%L0id7`B--Be;e2!|4 z(suL?Mzw+5RS{VIZSmqwgVnzeGWZLqn>un^g=nW}11)H}`hxlZw468UjJ#*+(vms~ zPu?~ZuBYf*I#iuvq{q6TiY-DRJCC2m2|q6r?wk6$oHU0t)9pfKd#U@=9RF5V8WpaW zzAWloFWd&pX#ThYTiUB{0zqREu7v%N65a~CVCcOd(JSI?C_w_H3oPk{D9UHVEa-&w zQDR0L5)9k$RXm2Hpk`%{eav!@* zD)Tc_9$E|=oAa7Sk@(&S5&F7$D@pHrsG~k}d59RDEj3|YBtoWGYPpZjBEhdnV>Q6l zD~PVr)H)Fd*FV;^D7hD`pZU_EsAPw(R~^P)SK9#FB-R9QBICeQw#GP^EiM!*d2zReZ+;2yZxUge%oEeW>+PL*`IpYq*6^*+X zS1ImcT%EX&an13=b)>_vcbtv>!3_BL^2AS$D-k~{u5$cBSa-|t(XEc_6u$u;-)6cA zx8wQS6*mcg-wb?q^Wyi%CB+|zTfw%5Z9S*`6TcTv-mbWjfRqg8>dF|624tqa`&(_rO##Y*K z(U!@v-KKHOvwgD>?>fdX)&Asw)`|j%-2oN%v((d&C^VC&0Xm#sA8%_PeEF9l-&Gl<83^qTTLsB z6HLR6txe60MNP$wW>XwqKBwWV@vdRHakpWhagL!jKGkf@RKkW_h9?FAqRba@rs1;K z1wU(f<}axXT}78z4WDZ^aWfr>6U5I#Yw@yB9399mP8WXC-+D!;Eo`K%b(~Cfl) zFOEfC@CkjL#OX0Ux3_6>;~`4Pr@D&$&u?`f%}Z3J>*@xYW2om_)g?5`)Vbm0XVi>9 z+3p45s||{GJrwZDxQvR?@SGdkR|eXV;vo%)n8RR)bCWA)z4KM zXl-1hzN1>9zOGuz$~1}3`9(A*EMP@CA4dN?Rgr8 zTBoV6w(>iq*7Q(k)eKSR(@f%boUg8oN?%vAi{qA)usd$36QR3|)_jL_7Emu{|GXZa z$8L^sPSC4zm1Da{%o#pt26NOljlb&(jaj=>lNPE$F6{&U=ASu^@@twh3GBkL)*z0* zCh}}7;Q3j{v$L0eqchsoI1u;4|G0wl@TE4y-rc0r(ID{0`BjK~b9Hjdt$EH9nWIc0 zF}6ZCUALD8rAzRNpW`z84I@NNKU69_gnyXGmFU-P_TQ&KdR|p|peU81H#b3ljP9me zbVI#`9OC929U6WrVVOR+ut{G|*r%^2oYr?BzcxU4te=Rda1nX7jRbuT;48eqYxaSV zT6oW=NHVYC2s25uSc(4JT4drHlV@x#R1&)g^~63xOL36URU9SshjBTQ*ZyR2o-kKT z5|)Z9gtg*2VJp0zJ>oXuh`3ugCGHb0iienI9u@A0CxnNvF`tTOgqPwu;f;7+crRWM zKC*pg`@;5>)4p=v7cTRe%YI_M@IgExycG`$uf)B=Gg$eLc+cGxH*lM)x$ULg$9&63)n`tia@J#IgJGocv-ma4F-3gU%N4|hm}UF*?Z zgXOtLe@&MJn}3vkwXVH>s;;WOKRoK@WC6>QWzPoR(*%9mrCq3dsvW31jj!~N&vY8x z?B3+(>+@M(7@zDP53CH^?-@ts$9aXVgsMB5bweB05oK9bq~#dg&nx&Duj(UkGm^-Q z4mXF-o~lL5a7S1~E_1n12z966IBdXAT#-~imhvGFigYzfTb z|Kix{OQ==&aHv3dDw(Thq=NDNk;j@Cx)f{{N`jZrg-)fSBxp1+_8tbt1pnb^Z!j8S zRgMJXIWqi2F6&Zc%s&m zM;QjgvaYWPz7oCSCkJFIAUQ zN}1$j_?p+fkMLRSmBzv0Y(;loaX5PpxSzj0Ved`POZ1iF-Wi_d-X5Oe_`#Zcb9hR4 z1y3sPcemoX>3;6n?LO&Q;9lby;-27Xc?4GeX4gUI zQr8x=hgER)7C3vmra9Z8!!)9cu)3?Svy7`7T15wEPV|b5aP$6hHFR2G=n1ac^b^!{ zhMYB=iZj9K;eV&Irt`P6HXg#d&Y#W(Y)#l&@^d>*@5=doxZEHvKZ@&3=K6EFo#ou# zKhBhJ|1!8vI&+|b6mmV`em`^nURZxxS36e0?nL)|_bjxM&F-`AbM9yE*KU{F z?>2e<@?`fE!)Mvp)5bHvGu$)Zv)Hr8v(IzKbKCRV^V4JX8qi2`(PLS|JILF^o8+DD zJ>uO;8^ASh$os>aNis=QrTkJ4si8Dm8U&?piS$T1F8QU`5LH!jH95Q7ORg=?mlNf~ za+3UtUI{e`&RmL4X@Fib48>%PGD5ketb^cqMTx_ATE>^c*NGJA3||-DJ{(U^eFtdp zeBsLh9ksqc4<4yT{xy)qFOl2&1*JTm^->uKew|ql&199dFR+O9+X=`qA9(c}$=equ z5!))b442kv689g;+Z#ekus|py{@Lod?fQ_XUBue*7|EhHA!Aq@E`Ydh41xtnm#UBadIiRq4(zSO^2|5m0AxdYnwSn~F( zqUWIJyhVSFpu?t*nNhuS(Y#ZY9*}mdt%tM5SqL9>2Wg{=sNFB=;_nz`vf?N% zMe?XV`bk$h_eMe>p2xapqe@0iG4r{dMg2-ug5!eP>KID110^6UGxs9wt*WTEsvDuD zbfkr#KgSbe)ZgHSDzq-@pr^)b_CirTq0UcFPif6vbcN^YhMJG+*0le1)p*tYG$Hjc zrvBrh2u*{YF%PP0k|qP`rmTE!=RkAGtJ#i2V7I0)6s}@8rb{qoFU9dsX;>U(_)IU$ z_UHdH{9KyTOLBe*%|TL9`!t2PUVg5hhug`)?PcM1)1&32;(i>O>D=c8?t6qL4B1sy z_uv?+J=#Z8&1eFX8TE|t|p&44V7&mN_<;hF$vM!C~giqr+hR*yyfuD zfnJ{(PNfmm-ovNI>rh(AA^PwE6qPl++Go%xGnlr4_T)2bv(7Jpu96jBm<=UA6s#Wl zjx+TM^pHzITj&6vFzX>z%n!bTsehB6?UOVQ?8MQ!3a-U$oMvNCQ~E)7?Eqb*5n0Wu z=nln$uL8M(_X6qABCL=`)VN}0^ogI4fZjpxr%O6;6Gi1b`o&S`8GC3b*c|8(Sc98w z2}F&#fqGn`Cd}h%^ng@C5iN(4vNX+uCDC4r2Rh_rcj+?RaE0+9qA4mr#cy1qk0iL35oGJ z$3e2HI628CXpe(b4VZxSV=^{NeOt9v{Zn;GZDfs;OHCiWI-RB~PM%5XCYtrE56-A( zYhJ0ha`bh9eZgz?D?UvlO`NvBCckzz)YEO6PTEVFQQG(axpQ{oYPw9?=?#e^S=$Gq z`!te88z2gu(l(||q?gXA9nZRM2_&K&x^lX+=sOQ}{b)p)3dv{<9Kgf6tomCtoP5+J z=>58udOJ!_cKs+cpV_!`R_VL3k{n4J>|Fgdy2YOA59&Xo0l9H*Me**KgrGh(tiZp8 z3_<}Rzfc;-PF4C9>IrRx=B!dX(COHN6iXs3&0(a4#tJ+5>98Ij=$r{eWqc{$d;9#b8>nfaXa0RTR5)XBoyJ;yr{UA zHN#5Q$4l52^8IX1gQOvjCyz9e%MRi?y~&Vv;Wk>+`PW!TE!O0=;T&?Dt{3wPQIc*7 zd8cH-AbcV7_L?>IBjJ<&y6_a=uv@=cSgT(^zwKmUf_|tlK;Kj72v4@5 zzP3=2>{J0VR2j%p5#ba3x&S>|-}H}kPxR+?m-Ksd`}HezYxUD~v-E>>!}aayx2>hC zr!NLWG!s2EMx90P)ym}G-odxKuG_EOuUn;Ep_@V$tRK&0Gf0jVv?X;pv>EVlXmzsY z2dnVAtilh`W4l7TSTjzG)~D^ntE46>djXAAn-XO`sD7+@uRcb8XBD3mV>OHMiH&4s z+Ft#)rW&7wxjDYK@d@cwZD-{>i`8=A?_qH0dGduxJ>j9 zXS60Urd%||h|!0%2p&fjT@UeK8oYyk!NGom+5R5*Xp7-M4G-OHp+k^Q+3xjDxBk>M&W?xhXZhUbZ?U{lXgNEQiyaoTDh$jVa!iYE;7#7@w|6pOD zRd9TuE?$I+!M1@S!8)*_%AzjkWv`w-pb1)7XGHzU9F2WO(SAkR=waZt|7PI4|6<^% z|72jd|4`r`|DM1~|2FvE8w0caYXXz~D*|KvO9I3F3j=h-1p48J=mlr0JG}cY{^@~E zY#sg60v-6j1E+QH&*Ze(oHsAfjqCLECk6U)8w0qlVch0uZhI2kt+l4t#y)Gdjvi=CmB`Ah60z5$)8Bwm9KaO`H#O}C#T$``2V8q}YBs6EYC z-Hub2<>;|J3e-gWn45U7Tt&(Gfw$5Ehq@4IO%qMBW|$_mb_KlqQ<`3Qv=?br+7skW zKQLP}(vw$2H%HrAw}?IY>fTdA9;KdC!JhtzXDQ5Ahg zm_HTpbG1hY7^j~H6LUX0$|K>GUjDPE71Ic1#F9cAbcQkFV6=w?=n=coH*N?4@vD%= zpn<@eQEX-?gNM7BINy*c?ljC2uNXFo9}K6(5Lx-ZjLBjlqhP3I%w*_nENK{JtZ!Io z>}J?z9F0b~(D2B(#qa}!JF{T=PUxlq4=at|x1!DKfLWEb8EuXJOR%UH$qg~`MV zV`kGGD7EK}VVu)H@lHR7VSB|m2S(O7+@pzn*AWhNLt__XW!%(-VcljnW`h}JqpQ(x z_?MpImxkMeU3j8Cqy!d;=uf7FEad?L@lX+1y) z=vxZ!^p%7wC`vo^c3RpM{cx1KcFc6D()ONPzXY%KSlZ3HlH9JVzd(|Bn=Y+>K1$$F zc)@LT_jFaE8Rnr&%|VyEPupMj7B}HlNM?IE-dIXfdmK&lJxDz_&|YFTybFCYi6r${ zt%tUV*UW&g;4t2Y^Lmv=)K2A7X#f-IHq5INpimV->CA}gWW;~1s9UlIs|xS90LMaU z&`x#CcwJ1!Uo+9YuI{crqHYdzB7yZx3ErhSP^40^$}y;G!A>rPUYir`HkJAT>zT_; z+mEZh^11bl_2LakZYODo*u!day=sJNG4uIpaNb6#+N=7L%;>@zv!$wrsvd-^sw7!U z<47t*<8Drfmzh*KV8Ugk7cC>JpLBcq&zUGck7ZOni)CfY!S{JMN-W6v#qlqV)Q)je+53TO{~54L61JADof*Db=70m$RB9AaPpf(ph;WsSfy9bVO6(6#U6~$ z@x%DLE~)ymZyT%rfD-OjtygQ95vG9CnVnT}NtEnb9Er9;iSMt@p_!;Ii+{a=W;?6k z)9O*02Y5oisyDM5K8X+hKK*^n@-&^Nj`kZ71s_MT(8_)=&^y!5q%slq%8wj^J2K%O;gs!|w$S53w zk#|dIMmBu_?}*vLUmPbEK<7;mUkdF-Sr|qxeXf{?xk@$h6#Vyxc*nmpt&E8~;I&;8 z^BG>!7wr{W7|f9J{w6zK*02P6-geU9r}^ogVJZF6yWm-0Fc|0oNN4=TG*g9>JSFe> zTTE!zuO`H{e}Ag{Jq3e_h5e>_@ytk%?zK`;3{y^f}~kn6fY*Xu=pZX#avArK=*niiPG!pELyTE(<_YXUXB0RM`*!n%)ID_Z(bsOndlyKK_ZmIeQzoi<9(%`;6( zUi(gbDz{+YY-9R0S@Rd%)nrtqYvktFlkXUbr>8N!HF?w%c<;5Lv!*y+NjqN5AF6J6 zHY=#sv0@wvf4ZsaTC9L-eaxU5$&pH9bo9Kj@U!Z{?a#?-R2}OTeTCcPC`9^2 zQ905#dM{EVx+9Vm##NYf>oXiC2O}xq^e2aVM{eM;-V)A29yE-n} zI<7;;J}-`3Q*a7eMh}#M+H{oWM=P)g67haC;wZCd;Iuy_N1zJ5h%fy8X?JRhtFa9I zhgtl+{04tL$f!l2qNeg+fUdj~f@zZPnr|FLy6^+60$UTFy)6}9pgUTUNA zUXIB3WRHAa{x0vA-^%~Uk5OfB$kR1O8za4fOS1w{vZv505D8`#`cJx?{WGqX(Sx$(RA;O zg=sxOz9>zGZ#|uDHroQWC7iaB^VV~jEnIdN*EuNPl}_TY_d<72R6J@{rSvf09${l>;ujRjdzvLXgkX#b4cuik=rIjzg(wh#iF}?=M zJYOee9huq#_`)y2>Ush{{JZZo%&dEg)&CJ*mZB8%n|w9=ndtH=>g(sP;hX4h>r3(v z^lkG`^PObAa@W7#_rZU~C;MOdi~+YVLqPBs4rK6G4;1sa4Al1bWt}sDHO%6`T>nJiP^S3UU&rYmSh6hW9<^*eoHUwLSj==Q0863{5d>ZS*C3InKqF?kt zs32*Bs-Zh{(Y-=*`cC>lMvc;tM6jV4rlYMcXZTU5NceN8LRb#fBuU+b*;G4nqupU| z4M1ZYNuTLNQr9za8!kkN{L^l+9#_}a@CJIucCm^*#@7YbJGc4wY4|G5IXA*T!gu~# zZ;|2zB;&fPWi)6?N_=m3h_mhcB?V~;{}!dF7$!$(5{!`nh#!Ye{e*^g8Yj|>$L z_X=eXx5R&0gTGt}bk^+bTjKevs%RKZW>xqKlHzqHcSnMKL;vt>%nw!#jSl7y^?-%f z1R_Ai;7{@#k7znR9gGFGK*OIGe9S8RT%dh$N1%EziPh@3Kzy(#zR*T!sAU6J0@(t) z$dfO^H#Qt6b=$yo{AUO7lCJcdSY;=}w0p$9=a|1dWY6rlO|AYe5Zh`%K+XdV*$U;r zMW@yy-vr-LUr*mkNS#!RVCa8+c;((7UyeW~QO?YPl7AnDVqjXOY{;I@-m*(<69?9>D|j2riRc z(irANou%vaNgraGv_XoO=1P9=DCsRdepkFrq}|@iQj)g-+41ybxs7DSecoc;uigw0 zMfBdw-ek`q?{m*4??pPKcGFqC9KOV4Pd9HO^Talun%)|oGTx$|eBLa0f2|&eH{{lO ze=uKs?*8ey=6>rr>VD|i>b~Y#?mq3A=|1Qg<=*b;>t5$+?_T0*>p8%-|GzCC|L5oDf}CEM^NV?obKO&}vYvCUik?fZs-Ekv znx4C^`ku$Grk+=>)}BwUPM%+`ULMIcz!Pze^oZ_Bo_P0cPevYZ9v*iI_YO~0`n(&t z&wDz!Z{tsY?iuI)>Y3;Ede*ts-hFO|_kugK_p!T>_nW&Ce~*S>b>C!c|Up_QpA%}O5?3SHouwF(A$?>{S^8YSCjKU%yV@| z`b@8)PEyO+r3`X)sT7&{Ch$2D;d9Jk-@Y9_#}zqV{v?-V64(}h{zSPFe*F%xR>wn^ zSqJy^oKi`7&nKD2w*aQm4ZUG1&gC<3pRWV2(wVRq5BV~{9jxp(L9r7IxUYtFUin&2`T$+RNPj4U_f0t(92;IQ>`5bSH?{N-ujFb4Mf1<~yL#3(1 z>|=DOBRNhfsr*lLbhu0_Y92kwbwiM@dfZ{@hAqH1$2kv5!lWIXPua!RrYjrhxI|ebSTZePz785Z)?bg|~tu&2TcZ@~%+^&71 zdBky81kPY?T_f0nBei2tz&GozYwzHLkl~AE)K^5^?xyRfpQBrhcjAQpsqQU$i&n^r zUnK#rN^ku@VICfg1Nx)*Rh|oqJ}9K2?WQa&t9Eb)$Dy~Z6;|OBIU&Bm7ZMgEF})av z&Qie8Lac2VDt0q062}|%h${>?!~^t(-Gp29(U8^+#pj?Xu8y@8gL6APnpy zB*Bl72R~sNU^;0U$~KB^9NR=ro5Fe1xXcXGA=UtYRt$UCcCc;c=MCJ$D$ZNVW#+R! zn2Aa&MGX2@v0$}@dzo(EfCF@Lgt!Ext)31Z$ zwus)bY2@Tb!vRkuIo*}{OeSu-72KlvC0YS za?z=i9X?-Hc$=B@A6RR@Lgjg)OG6t}DyBex>96Wi>MydLWjn>s$Jkh*>ko37{kn88 z<}xtH$i$Xaznk0It;@}ppPviUfmVXcl;yIOm@45ALrFTRYpg$`YlWiJk=yR6zX_A> z9B18L}NkIEMpDRCSwPB?S`Y9&WB0A&18j{pBo2s zHM+0bn?$tDEHE@moA;uX-Z1qtf2O}FW?ErRN4s7L9MKKU@9Dzwo2Qu_mJQ}ymXq*I zpO95{niDNX(&yPMYb+HkhoDQ`v<#vba1K3En=MxB8A~?nQ%f1E)6x*fba(R5qpfAE z3#^UFqW7|%z&rhif%y^Yg#tChWP4@JNG83AO|(@bU){o16nbV&+ZbD0+gzN~>ughO z`)tc-D&JwdkDvOz?XJzmzcHKJX0vNZq{rLy+5fhe!ChU$Ud7(j-pJmGoOeIIM%qW& zr`V_CwO(vrNkV;-eFv%ZL-wQgvwU6S>%RT2{e}Ih{e%6r{k#2>-APVdwkJdT^x9Pr zKy?Hl42~$Nb+yCh&^jCry(8Ws{I{j}zyISn&B1wAE@R@dBG=J!-6*%==e9iD@-O>G z`)BUswf(XEk^Q#)ru{Ox_*3>H_5-BQxAEB4*cb6N)jrKWoJZZ$-q+rW-=Y?2^-}y+ zIqgO4DeYP9YP-Xp%f)sy6TPIr%TOFI(R_wp5 z|1+!BcFOt-y2t~lBFC&f@U7RfPPXQ?_OzOzko;>YNDo<@b(h6!nQeIjVc;0<_Z2Wh zM$&cG%JRls%5nl{{8F=I9%z1Ou4CS3&Q7aj)YKa%eS&!(ucH}g;T`EtD9L`_${zl+ zu`Ax?@}>psmAe~%^V+JD(eN4T ziXI4tS9#B@#7o(anM5VA6FkVmXmBc&===Ja!Unhw1DTCh(uYx+?~p85%RVDfKUP;! zUsq?>r=ySOhjyRtxOReWy0(F?xi*t7JAOrv<_tWFx!Q&3iybxjv<2x43af8uZmCyj zR;hbxdNVmMO-9O~e#~*hCiKq1>fx#?>KZDCIu%Zqud%(Vqp|U-=`pljG}zqaX#>&I zO!DV)Y{Fy+#&{)q3gSrde8K^66lVA=SmEtpaTO*Hrh~iwJUoT=q8my%YrFG139*;Lo0Q5yjL+=W?r&( zIU2|iT#R#K0FK0Z_&oB!#S#N8n8pKd%8Gv1ZbVHj1ryo;nw-ucoR?;L33O)Wq1{w=@v7Lb2=OUsJ4x~!2J$u_B-oL=fB=SI&d zfo@X`Z)anAlslo(3_zcmKqvYF7+CA2ukwCLM$6GF52cjKXDPd)NF~r_5|s3KL<`D2 zl&bOwrMWyy=?#l-yu3?UET5r!;hu6v{y@Kim*+(AMddWUlnN~fN=084rID|d($zOW z8Sa}-Hh&%b^rPhR?? zweO4K*XF);m-yG!A#wBGPM{q01d7uXziV zB(j|_FxH0Oh8Cc+OvJA?D0~WyWgi;LCYtM)g^Gq}g|ddn!b43Yi`FUh8z<*Wyqwor z!yUo#un|i9d@`}4X~OD3%V1;LE6Rs%&_sS5J$E~egUe}}n;yJJ67B@#zJG9M%?VZs z4kZ!T9)@ZH4z7a1Kp<7{TObO>?<-EPJAwUyBlyAAK-HLnJFG80wI(>p%HS)@5-7l4 zEL|YkF9sgtt3Kub;QxmntXX_F(7&C8?^1sS_G{Vs$?R_r%dHN%_R{|IxLUXK8eZU2 z`A5L_>)?Ckt3iiALEkZ6-&=T{FQGqRGCJcR*1;X=5NUuXy8?{={JsuK2G|v5dPhP^ z29z0*eV<3Zjl1@=azoxjm&rPG&V|Z+c@o;^5Z-&;;bFF-Bd{)P%!vcU6LnBC*)z$0l5$B_l_{|nn^3Llx=_HxYIMh<&h!U$~6{-v2Ld7H>yZ)4f> zf1CHepPR@s=lQ%XA+W>N*W+%l!l@$jE2iMk^RyP*nIQl zTGBH3eCy=)(l)u5w4cX%g2#MGo=?x>8tH|+3+?!%fo&eptCGUsc|~4wdC!^wGq~d8eS~B^NxC@~}-B z(<9jvJ$M|wl1tDjcJZ8DgpKmjSH|a|ciiM3Mi1o@dR-3tTKXUP2Eh@X<4@_|ie_GWB(%r-{ZFtcxC2CyJ*5|cNBY@ z4Z-V>{eD39Glx=z3g8!P2)%MJKF%fVSB{a=e1$708Y+g4*^UhObkeo^=@5Pi-A~77 zd%;MA_ir|Q%(WtWp@P1E?V*dFj1;1wj>ZxEsRi(=Hf7#2Ix5CC(vx^S+8!tCEK-_> zVRpQab;AQSH&&9=aA#(^^Hf@9&Usaz@XFfMqhVcaRCQC|Aa&$M*GNO6yRzCw;;D>g z0rS;kFuLC1%@XKBETA!Io0HBQ3-51}W}5bJR6!nMai0P&q1%|wpf*2C{UBSy)-WTLmK0`t(e@U%v;=2$!`B>E(OjGG)SYga<+G#9lI*qPz*VqI#pd)%lKT`}%!-g7>5idqQ==i149um+Y z8k-KV4m!aU^c;%A6|{$2Xc6~OBOaqlJU6YSF>I6R4VQe!obx>@#(NZv_q2$=r*G^n z*LrQbfbZiX$GsQfd0nJQ>;g?<=V&53!`DfCAb;A14>CpFLu1)CZgnHG)79u4Nz6&- zpqxxW*%(cu`#@85Q+HD-vZ{GdJJPX6Gjl8*GUhS;G^RJbWF~qYn%FUpq&FMi7#A7u z&?a^Y%HB?9q08x6n`$h;-yyZJy-{tf%>=nPtHaE+mK)J8JoJ^nHViXdhC99o+V~Pf zUg+cwlne!>>9ttcaDiuLs~8gJi0{P#FvgoQyDkM?Jv|h@sL)jWAe1C6n3hyj2&d&6 zp)WJh`lJO5p-RNj>+IE6L*>d&T2Mz~>oX+y%cuyO(F>-q>h21mttzYYtSA5~QbzAc z1)qVpw}!^L(YlPJ$0FKNxD-?A&O)|d2aSI`iL;L6&?>o09DUUGceF_A z)V6KAO>Nt5%G6A4+qRk7PgC2rncW@F`%Ay~{jOZoCTq<6=bZc85bUbv9+FD+ z&TMb}HhYoCIRqBo1m5#pSedKv$Jt@kv7)WEF!UoxSsZJIN_|LGLk^zB*PX>_U z5>7|_kcN=MJOgK>wKy^yB1QcwKdoxh!n4*%J?L17A3Tpae3<{rJv|9J?!rpq^bC3tj8Jg1nwCRP-OlhC&CAN zHwy@KS^1^Z6uwl1oKT*E!geh#{72zn--3hv37x$Q24D(Mv;3fA)lm1fQU1z<6d#`c z=|TO9!g{HxTtUhEQW>noDpSD-S1D;hsSCp8stTK-x%)dzK^LsAF!vn~~JeiS&U87!)I5#Y|!ynM*Fg za!&&oGHppN=;1l+8B9{cSUUW;toMavO0I^Jwgvysy(Aq(liF~W{P!4--*bzz#zWSn z=N_B9Zx;y;g7=3<^!|ar5n$8#-QqGROogef$iod=fUIs&HvxnK62R`ZvBi~ z-iMcS!(;N?DhZ0Qyp~^{ou03Fj=uLS^SmV2CRq$NU0U>+5=ot3Ehyg`}(-q9UxW^K6+I?V!7 zn4E;XlUVUFd)W!JnQLIoji)B)EVUD>Nfl56XJTa)@hyGJJmjLV6E*craf;BFnxd{) z6JOBW)EA*}PQJNrplIHO>R=Y?gT6w4{I?o1XDI@wJ1H4Xe&>qofwRqZ)R~H>R(ID( zr@E`HligJTWkV*8Fv&SjcH0K(ohWC!-O-t4S91E>8J)(q%PDTZLM3sAY}K{)8*7|> z2^Gb5a#rWSK^cq_T1&KG6_^cWwKkDQJ%u%-2kS{K)|Py%GYRRR{+N+yb=tC?RX0~K zXC8|$d1r8yaPzB?*Sv2ervD3|?|g>y-#L;Vwi^AY=~~0|Dta$eaP8oCH^PIfI!+*Em?{?pk;x9n zJFR|MH!(vKTBs7Sh{hUy}(&C6>{Nd22!D}XOvDLp$#QUUa>CAC)QTf6CX zw1IFU#=zO00WV^yK2qDPPt^|Ui}1jS-0{xV+MZaU5(BB)kbR3X(BZ5#4&bR5W8OAi@-6yF#*1gA=PXzf7D6MlJ}iT7bj+iz4d`)BnLA-J zoVWbuQ+)OQaz+iYYgw7?Zn))7uo~f--^)H}MUf7&+*YjvxTRdRi`Z}N2AnA(IHS)b zO=LR`Zv7phBzUoOaqfkDM{iFK4CWLz9yUwq+?aISpY@_Ck{rMPV&dyn0TAB#8+{>=q6(|lXO)qLpFC)>8}_exj9RuCO0S_{P2oY zOpW1^b(KVBm8sr(Y%bYo$k(c(9OmPW~cQV$J zoJvd9lGrSWj;#FXc1Li}ooo@_Ob{ z3wVD~$|iY$vQ+NCJhz@QS}v>flXEE@m{T>xOM;J@Odo2db%i_*BJxFKckJnm|TW4|x1_q%UGY=`Q}rC&U1`>W}zt zkBdX#{{F40A9aDM#D-D^u{SsEwE6wYua=dGPvb~(JJ zGC0#<{&nLRsDgqr0~M@kO|u`v{yU6Y*?c0z6( zRG$SqXQ0tWZ)Q}`%NUtZLoy&YKA{1*_HS)D!a9TMLz}2~fEid_YpmzfO6y6r%(|h5 z=x^a~T~(i#48f-z)8UM2_SGF-RI z;H@#_Q(g^B+Y5qe zNnFnnTpcVF+=%Pl_FxV4+fB*C>_{$VKd`OQUTAv=EC1L z3?wcGn$9xdCJjJPx-wrF!I>#82Wc}ihm%?YcvQJSajSvib_B&8O-AxE^s9%#RPO3y za8q5X$4BRy$GE1~B%ic9YRpMSTG(+VI1e`lPw5BVI0Gf-78r0Bj5g*gW0YwbE6mjN zA|>f(nwmPA@znGcrO6{{1ut!c_03#P9dOLbMK4tY^-eb{jXlXKV{f!t+ZU{n%wAT| zCmcsP{?x8S#%l+-Bjd<{U1PU$PTM2!+*^*naI}-2mAxFbe_MR^MxhX2=`?X2cLu?; zpXbt@z5l#JOQKh6?h0WJltWnLss_)t6WsT)!Y|hfAt4TN`B=+qqbTSh#KVCmCm!lm z#ItDPUchxX$X-Z^2YgZSKU9~m(Ie=1*n~+N;klm{t4dG6X8ht{va)8-M0 zF|1k7peN+kJS8xOvGEd zv%rzghGHZKDPuXwcFX1N%hsQ5Ab$_$@<<*b63#*-ZY`0xvJa**9*BEOe|nAxcS7zH z!hL0M1)DR5%B1hN(%Ah)sf&|)IF)k+_Z_9AJBFU+H2RanN_remlDgNSkX+1bok3qS z7A5H*Fc|V1uWjH+I3KdWWtu1}}x(sNEJ& zhYv&d)($Rs6?*Jkb|+MEwc&*qB?~#dectj=oByUie~QQDMXLh|CN)4d3Zb@4g@c2L zKixO_{rhH7>ohpPcB;08%sI!x|Lkqv1EV+xmarF$VYQi-gryLYmVzW)zc*T&_l(;3 zbeA#rQ?su((xJ{yXii{$6N%GuH?ogg7>|wG#x+pcGh`nhWG=DINNudfrEr0v7}L<# zk2QY7!u_oG#ly3U@knn&rPRxP1^bCWyE+yOdM;jp>p^i2GWog0 zr~R0T)i*OKXiFa0`&FzQRx4a02U-nC#p`NqMEiKcngiOhiBziNB+%RjW%*>4v&oAB zVHpTgJqtu@D_)f6$<}+pUpk0aGUpUJ_J?*|Qn`CNK4+4X8Bgi5&MBvv^MsU|AbVL- z7M;GoH z!fTKfi;6il?(xONP54xt!MEa>FayudEn*V!9IU+;q)zB!7gR%IsjQZZwWUL1H|ZAL z?N@Pyq)3Or4sYTu`k87>lKY`@o+cHeL#`*Eq(goHGvJrBME2oslv(}{E&B^_6hFs& zJQUTLQ4E!YsZ>w7j)v_W?noxpaG0`Q$<10)5e%gn>q}qdmlCB&Zt^GGsNP@-m2y9% zBK@kgh9%U8^=uUD-As3BvKQ;Rx4YZBqum4C|DjmFPde%w_hwi`N8BctcuAoM^Lzw3 z2%s!iJViZ8J>@-_VTu*>G~~CIq&s#(ozlzGg}lf9;1eT3P^N&d%mYPP3C6MsBxMg6 z#!*m;vt(e#fN9+FZ1z0zYz5cY4!*GioMR`*%1%#!P3Lci%SG=tFp@1EpLY`(nd?3A zy=&knt@I@HF7+hxE?}F@HjTe0aNTH6Jnv9$+aG+T2Y5>dP?;9sDGkWk42QQ-&hwqb zmX9DmufY5td&0m{ykIG&`z+{CH0r`Vo(Fhr#h^7kj?dLD_he=TBj9*M;D6N#U42te zM|ZfV0lL(R?mV6XXzSDC1{n`My{`O#)%Kj3!!Lt$vv1}p*?4d zT73G&l@jiZN@jYJ_-?3!#Iz( z70XLi=+kn7>ct1C{v%w*0cIPj!x^~4^aWF?C-xBviVe}l702l z0P9%^CR@YN%6A5pu7}HLDJvy-i^a72t!aP(f5OfF9v&!XS+944t*kI>o6|s*hca#M zhQm)2^Ba@o2TaCJ8(Hww2%`^>@WzkD4d?@_+(XnrF|2$ia0%LDgy4>%>Wf$}9EsrYw{z%jM2UJ|WEL8CDasx|aXMp>A}h4e5Z2UTS{wDw7L z(FnywMbb4e7{4B?{U)vR2QDe!NVWe0Ht`8e=A-^V`=H;|-s`v6Zt(kcF1yBc*SO6! zZhIXp;2qL+Ut|SKIR+4_0`4{eY>$pKVoduFBk{GKrZO7jC=YI620$xy^h=DYK$ci{7b~*`2fENUGtv^i-RSHB9XHfrXqmKN|O$Kz>3eVvsSA zz^V!+(gsCFB+P@^c$9202jX5c1w3Sh6>IJVX}?6J{FKbG-&T9eZI7_h+KWjJ+-KFa zuUqY5Sr1_{In&NUifRS>7?X_KocBN3OF-rh+9^ok$j^kbx)UE?hCFyI)o^CvCA-O) z$dqKgbI&;o3o6b})?&|0L+w!t&1-Wikss>dhHoynKk2 z|37J${8&1NL*+wsqhHAya+pSiDf#5=N;$cdQWr0cHgY?qx7?2`v~kKbxTQm9bquy5Brkuo$_RpPI$>mPVtTa2-Y5}}FN`Zk?;;c|h=>mq) z8(d?MyCXBy9?Cd8KPI~)l^O1lWU7r*7LviT6b|YNJV92w=PPT$hu3q4*}z)25ww1z zdkw#@jG{Kh_TpM(hg1R@!5cTN<3xi-MbG62Wy|nX;RE^Ux{nq0`GRZk=cXyW9 zwVWYfhRy7RPBHs8>*+l^);et8vzFQitP!MgwzUUY73{_!*hQFPg^~XG*SyR`Z@YDn zq|HU7Y(~QHX~u-C1d6FtrfTZO6WpXunx~AlV4oArD5D!H%9>^ks^L6T!U?DZ|L6}{ zFOM0=^fmZmOu!$byU|0hW7K7hECMqsm2SWce5HTDSL3?2Q$M85*H>!8(6zPKK}Ymv zT0XskmQ2qTr`!OGw!n=)k(F{+U89}G3uK2nP+Ow5!P78Y8wh`*gPKmOr+T$=aB%X1 z4y0ADtMSxhsu|p>eh)52FEWulmO<);U?;L|8o?E-tga3gCa)!nIyIO~9SsgK2zUNo z!EfLdFM}=6nlwO>Su=PX9n?PXjLpH~!Ii;+;2e2_)4@5Sf?0wiP)Q9$o7o56M_2M2 z+XqvUl#>F~BSo+wzt?4}6-*rr2OFstOi#vgCLS#-+{K)1`Pd5acZp!tU^&=7RX|K? zq4;b_YCuagpdDe6^n&X+h~Gw|&6*O7Ao(*gxC({V*5E|a1ZIJ(EGAcAE$pG~!OvV0 z44w`u>Xl$p^#SkfEeO%CU=`IONh1_AC6ziv&4D7dxVlWOPU>hg^%NSK+psO)gRrRT zay1@qEtz1X6w_|w7WqzX4;mlGkedP1X&s3jM{qg6f%DcorUV9;hM|MatslYf>jsGY z2hey+n@(221~O5P!&$kQpqtnP&+QBt%M){e8A~^o(Avh__9A?<*YMFSa~M6{ ze5;tX(`v|Bq%R!SX}I!jvXa_o$*FyAh1(jlnq;t93eq{&VfxwwG;spX<7@CtIc6tt z?%O%&6e>`kG-rk$;Z!8upcOd>k@TZ;n7(d9mwe7qImQxD=VYZ*D2*pUeOwc|a26Tv z>gSq`tIB#j77p`sg*ohVCi%bVAtc=Sl7oll6v}XnH^A?=D=6u3`i5EPqSpy?@OD^( z$HN}ZET>4uyDm5|1QL-Zxm}INfxD{+>YPMTU0y3XQI*EelJcv#Vef~|cEDEA3AUid2aA_dD z1+o7K+xUyvUiyxA#V?#){))X>&myFtH~?&55P1rbY(sE482WG1_+MoUa#q&iDz1qIfE15`MyDddc^9)Af;Dp1ZEytiP+M5oWrUP%%U?Rf%x5fSD0a4O|RV zJ1Y(uNnDD{g_p$-ddz3eMcf_tI7dlEjjKWyuvd+z7LI_S(Z&z)RhT5_pl3g2`8iIovRf-Z&h! zBb&YpDWcd z608pB`HfwumA9}?tOLJ@)88+J-L?QF^IY9QeXrv*=+~#{zx7G1DHCD8O@PZeL4V8k zn(ZZ*J?FY-+~z5_ead~F!p?n$58pHJ-52^Q{S}PQH>7XB(|7A1$%*)?pJ0l59>w%E zG7#=jp+DCps(l~$OhRy)ddGs3%r?e@ z@XX?!F9#XfV%!EJc?)(N;5RR5aT*lF1@L^TLgrR;(i8f?-W`u_dI=rLPCAqG=5UaZ znS3K_Os{nSMC!6x(t2h#V2a$6xki+gmc;HN)* z;$S!=3-RncC`=OX37f=N;UZq>@A3YWVfba0BB_EGu^t>2qnPrqXWcj}rNDE&gycxg zr8uXQvhq@?HIu60AjwN$3{b1dcjc1uFS!MteZ#2UmdG_wSam_kI388kDkUjsa1l`8 zMqnKMl~5Re*}*s};f_LnCKaXIJqN6JtGlB6EZmSMBx?S_FDL{~NM_FscWKXOcSCTD z?w*XEF*s^0@HBx_76IpCs^_j}9SFfOcpY~^6~1~hqW~%9O@l*6VX`%A;*!!Hep)2% z9W(InSnJ)1lg9}%H?MkMcprOzql%J!es2n&=*xwsq>L|(uP*){?cfAP_{zeI3rFwQ z(6`Fh%C`fLkZ4$P|M>>N7aZw(O+x2)bblI7Dssp|yqA{yQiZJcWe!>I%M-HMS2ScB znw6ctsv&!Pweg;67_#5joDAZ&Y@OJ;vGwAz2(BBzZ3c1MNbWO)`wm5KHI&C1%3}@* zS;XaYxPF?iKYmsbA)~loB=?Wtak}w%ZF$@#JbrDSrxMRsg6GZ0^JnIDq~P_0^12*v z!jL~+Ipl*^^F8u@_g(eAhS7K5x5s;#tjA-%dEQ;VDDP_DK<^x1C+|34BeDQ0k^flG zTi=(#TizGno6l!>()d1m@RjlS$pd(U#_Xnd75cL2p3UANo_XGmo>AVqo^IZfo(A45 zp3>d~=+ZQIJnvh#-*X+0^CM(MuEU#rx_g0V5I9X6xSiF&XY%8LlLGCVqo{c0Ja^wz z&bg1^JHA?(PCj&myD$AtQ#dVUnG0v3pYXvr`X!$wFJL3HW)#kAa&*#twnE8m^`1<_|uB6^UfE}Id`0?&T*W`H;@)L z(>^xh=r}{$dTVZ_ytdq5ocE z6|*P9Q;4t<*v(-GRv^tK7mh1QK-Nw3fc3##XWcgEk{dXdzP_Ke00!MSxDS2I3RX*$ z0O8DFi*eS;0{b`#Ogs^s=C|>bv&#+6E@wg04>6P7#&m6!x!jm<&N8N$UKqc-Yk z^f22Q?ZFzFnKg~N;11Qy5=`Op!^g;OJC}dw`tS5D-#JtL0Q32Yg5$U0qL)$d!U!=` zs#OEtjSVg&npq7uy-uiE6tuf6&smN2y}psjY{_fs%xj9^wGF3=orE*QJfn`e8YRaL z1Ktn4(k0HS_lyy!J0^05oeleEnVHz!NV47@*o?=SI{e2u;V$Qdm$+(tBg0EGSEA_H z14e!h4CMiz=NIPMHq6T;Ofhm;y>5 zEh&sAPc?JZ z94T}VXW~P+T9_*C5mtb-@5ZC+EXU0~D!vcoJ*yz@AyQ^4-NICqmGJm)fCF25v8@zA zMLG)I{0wmho?XkO&FJS3iignHotJKix1^`yGmg_wRJ^g0BRUdtZ8@2gOwK4}kn^Cg zFD4b1D=$9enVI|8>XOe_7z9nQO?;|NVhXk_xN7%}& zWInEvqv&wDvBuRGmVv5Gftxy52o*ZvK3N~kql|FLm6KUZQelZp5XO?U&;y6c1~6Jn z;#Qdv{)m?z>lX>2kDRuk%r#tF!9V7ra2)1%TL}0rt)maAs|L1$*!$=Z4+S zIc&FdR)acEw(~j>c5?6#ms6e`v+VX=riLeR-rkJ!_FNdeBfuLw+somWOt4DWeXNXl zCWqMNVfp5?UclM9Y#Deezc*J}H_ge`F|)t5(QJ)6sv0V&0#;WT01aRmm!*!&Nw=Dm z375;<2j;xed<&H#Nbpx+Xlpa{3#s2rk_@NeW9(2cJ}r(L!33dTzBI_wM!FYCZ1Q8?>EpFSc?07JfEs z+xcx5m+WObz;=YcPjLBJuD!(Vu5tT2-0u78-i?oUNOhJFF} zE`0@F=G*BnqCufz@ZNa>g8v01+|-9N_nc{D0FNka9H73x#0>W-z0FT*dzlGADsvmv z`zg*mcbWfw2A6O+L&a(F@|lO=uwOS@oA2O%n4lF&*(37eLsX5Gx*Z((A$a-DMQgLo zDrcP|XWCiRj&O!vm?xp4Z+s*z;1*`4#01+<>%@zV>_8WPz$Zt;2cQd2Kg# z92~jRGPf+vl)MFa_7G+(3qk(&g8<%g3gM$x7iLvYGI1x7^0yXd_zBl!<}({zvEbT? z@IlPWEGJw@FLWkfhEc@?q^aeU3Q3jV4YZPava(Eq8?qLUxZ^OwAK-!bn=ZmE!r*6Q!~-usyzsbrW;o8(yR$-!8-t&rS z;WNB59>Ko8>&`*{kr76AD!A#1nKF7k2i=Z)7ojSf++Xnwf9+oAe&C*gTljeQDfdwL z?S0%Ed525f%}}@0qLV3)!ljVA4IGs^@KVaUeeOJPUsAij{qxiaC^yi!oWf6IH~+4} zOJgQ(8Y7tm_hcg6oS$m=N*3mm&Y-|SR2*4_oBvL}4M!)MZ)csnnQv(xX!uBSle&_X zRbS3Zl2QnB=wE!Z4{$y`PU6x!<`|Quo$v`4;>JFb^GGMUx|))~wEHDK!RJVH+9bY( zPZR@+vj_BMAIQ7pe+-a+IMr^cWR<{&*xg|Byx>#{^EW70KQZ-HOorym9frS zyOVPf*6bELCv3t{r?>sXuE+GKAnAi)%#Q*bdylPl_6e&Jm2NgxelLvNZzNvdwDMaA zsIrz=0du7Fl&ba&nByii@zddp^uxQXsd?Wl1DhceeY=Mo%O5!O-#6-;C#V4NRx@Y7 zi5O&jgjI0^eB>b5$O>?gC@_(p|MaPM!9-6oG29B~FduAVjQ#}n_IbT2o`aRq&*#_2 z!dL5s>vR*=z4H1ycx-oBsn3#IxmWv!!_gynZWqWd*oSg?HJJu8(54Q@0jV1~2~E-2 zSJBdvjF1rCwx}h>Crluh@{jsbeWTvT8|;dD5!L+>ezuVbze3$eHvCF;yt)wP-AuJ7 z+`D$ThBZ=$!O9zmmbH&sMD4ET#&szpde)R`OLW%FQ0g~QUF5T1YhiX&QN!mhn0nUxI6sd?VG!eQ3(uqMO!VAEu4}=NrBP-|$`9 zVVv9kLqGnQGuKyLWmX#ss+3;OY!rrlQytb#E0C}V&h1h980N|IVC`%)&gh5XpvG`! zdd6Pzlf6ZRcb<&CC#TsA)=_Vx9$w-d%_YWQeBUS2J1wKO*a^OL+H|9+O3Rt3Fm84= zaMNpt(_JKL&lzS#R9TI1vFZXdaR{^WDVAm~!_8_3S>Gq%+gzuActQX0lm5YF&*SX0 znVHaWkj7ga74NNvwhn@xfYg%A6twcIe&Q&5%V|vN_Hw$z^c~<#b4EH#;pc3D%YKLs<2k}F$McY-{B`GU4zAAQ5_0o-OBhK zdt6gd%grWNeW5Ep?DvGA^NBbcCU$LQ+sU?vZ9m%~E*PDb6s{8y3RVYUC~VG_B-QT+nwRA zb@W8boSsyw9m#NSL2^cYa@?!Adcl+Ez@g4!Hy4>{1|oIi1c-!kf~+Rkvg5=HGxD z-=ga~W&8MUG^T!^tx2qP1ML{ryJ)L6{;4JGWiYCy;{7p&2tmS$7r*sxq(#JS*Tftf+cqZORh}? zTnsEZ1D&pqZvQW6=yUMV|BSu3POf1soMTLeVbAak}Il~+BIXUoHu%TM2(o}N4* zu8EHR9}M07puDS4yG&zsj3m9bBY9_a(esz)k{l$^gz0@r_3Z$!q7gN1HEP+COs4Ya zo2hLV;+i-CE%iX^+D=rM4YjwVMm;1o<{InbS!Rq!&|mN3_+HPPahdi+okL#0WM++{ zwf*W~ZL``(TdsE2X0ujIP@8DONa>AGt81M}?rlYiZv$qK;c8y30{#*u)Xe|ZnVf1$ zEfX%VsntYUQYLE&RqWwehZI%PXmB+v=%`xIV!blah8wJ#I_sCN{`|KY>JKja$#p-2 zw)#8h(*6WREjB1?!JtRegCUw7OrVLZX>K&+p=xR^G1+b@aDGgu=GL;Rg|xh+4;N9( zYh~4HS~ax}>m9i_YFjcCx@vvZ2;Rd`=BMMyOPB#aeX+Kk9EBt5PVJ(41TTa0XmoCB zf8ei++GjqW0Ex%4UX0H%T+5`lM32)4Khm+Ree<<;e8v&_ac!)AQ=7|Lw@%l!19~F; zBHznHQp>+^HnaG45*d?VFs`P5Il?UXmfn#yZiper@v3odD1~mlsd1fs;T^TO0nacA zPMf*eYbsN1w=&b213A;qU|O*eT;vqU`9pGte{=o~wfs1HCL^7z7c^6p%+akY=s?o+0|0`>>5M5%?5G;FT=C`&O9lJ zI1B7zHwmCOh4JDq(rpssm0Vc-MW37$G`y6wn90}?(D29N7JBWgQW|nA%c6yAOHV%r zBzz56z!_;hp5d3Mq~mn&@sz5lCcD6An1o_-le}2DC?6$n<+0+XD$a&VvKpF}uJ9)& zlB>7@ZpJyZ@2`~?s3k2}%xOL4sAyZeTY84Ohk6#{{dLg2-*el2lM415dO@!@hbODI zil>6Ny{9!kN0Ht|p1Iz=U`IU2?Q{(cF3MZDSZ_bZRTUsL@3dgJdG<=aKp z>jm)Mr`~hEKi&sEFL?p!@%t)@r(aE9R`BHFAp?EkA(P38UFPcvEML4u`lyE`%fxxs7|?3$*25(7*?90~g7ijZd~giqJ73nL=lTdcoNbs^leC&&~NxY#fJ0^HA4o3%At{=p`pV<6NionO%XaOG+pSJ z(9EG@Lvw_V3(dn;fUPip7vu60TwjXYl@1*mS|)TbTYt7bY(2QFb7;xXcHE{#XwlF{ zp@l;0gys*e#$%KV&Cb0uhvwt4vxTM%O&gkopU_ZWsDfLW9TLmC_#EN2}xye;Ue&h`N1m^8E*Ne`p)=<;5^U;_w|OpSvV?=!dbDGFO{zajs(?xf4xO~ zue_Pap-$k7#)EtlGm3d+Q;+tZ@%ChX(G;)1N;nASC7mIqw>9&Q>fWE8!Z^dH^Il}n z*avfRB`Vh`p39y=p8cM7a5!u7S5dg089c2$p}3L<=$l@BDCT6ME=rTr7ImvBmS*` z%U?dFjFK9UKd+JmW{*X$@`+TZd*u3_fgQV(l!m49EE1kZ$pc{Yw1d%83-v(>^!wT5 zymAsbEt;tK;Py6M%TMVFe4c3f)U9O3E|HGV!)!y(uo5o(9H|+IS#|y@L01!({+mi_ z1SS|RnM|yI(AT^Yvopy~3%4?f6fJtCT};~7(HAdbT0KpC2gd&tOzf`MQ@lo(b3tqh zAE>r?SgZ_oR*J0ALO28DVm_Y^I!PqvbI#ClPn=l6j0#YX{z1BdMbN34ah(YDL;t52l5Yq;*W7OP$AezlH>lUF-?R*dt=t zGaj;syeHxPFMEuj+<{B`7Ei}md>q|MX?SBbNd#%mZ`~9WhNOf<;WarAmBt$O!(BM% zpHL3KsJ@^)V&3?HAHNcWRqaJzk`kZ&T;z_F=BJjs1}R|8*vC8L#6N&6hB2TFGvHM( zgIB%T9pm25F>%`c(S40x{}IQ@J0?`W!2&FB0k5Yloa$N}Pt84fU{M!`MO_IMcWrz) zn~{jpfvIFKc+rDBN4WGHN8dGA)em4)zv7xNFs1);WSYz^W&ASY>u7dt$O)eBQ^fyYImCzJi|q9NPM5-pgM8+Jg3e74KvLjPa?SrM%batTLlK zT|IqanYZ)Q@znEFfN@^Pli!mSAHHOscpf)e_aL9^M|kTGS#$n#?*{o<4xTcZ#I*t7 zC~Zi`sLsCyNfA#?`m%(!=Nm}UJw?TN^eqab>!2h@Q01&sT9O1;f#0&RCxqfH`iuSK zKK`J`WP|&HsgjZxxf7(7=wmUglZ%!2I`!SPv-bj$oq(i4!V{e%338$totrtx75 zeI-pV20Ua7+|-FKuh0o*N(E|>bo4a369p>X!F33q;rTFJ`-2PCbsmzVaEKJ*`Dohv zIpEQDI49|{Z@UoVgwlTzfVKf07EzFcFkvRrFNPDWdipEYd6{Z^^{D`-D z4Vcq8;dXe8Y49%k=$G{RRPTjhY9!OUgGkrF!9FLLhELyw`g}T7c|S1crXU-o@o~<` zZyu5#zpH7eOl_Q=->4hZOL#wT$JK2%IK&|MY%SCRYI!(rSyV~&Q9b_(J_ij|;j#8$Ri2$zKTbpY(_Lqx~XYggRbZ ze`9x(cd*_69?z|pv77x*VmJEl$FBF^ie2Zwh6mSWG85u*6V9@o=I;|+ew^#0V>kGZ zlBIAIFRUZn|7h$kw!K__fa{O=PsSeS_NU2RxZuAS8^hz=;PLM9xKDWeS3J)Lp6@%) z8|(iXYmm1f22=_c7ru!ef9gO&f0jTBf1W@_f6+iLe8dX-!vkgf4FlEutpW}37>o1J z>gpdJ=s0DNnzT>HJ49u%Hrx)tNoOxiR`9cu1dMDyguOyi9;na13Qu?^dW}x&8`lQuwMBEY5f8*DIX;6K zVd>{G&vMNE2cmQxR?;0NQV)$rFne2?ui<>XH@Y+D=!^g0ASU(0@a7wh{w)gj?__kZ z)9J|KCfD=m?HA&Buo%tPQqZU6V4y45m{*&t{%vmlck{Q*kC6+$b6UJA6M#j!v=yMX)6{qDqYrRNy$s@dT&)OZn_pc6 z-+4ZM_!GeshN>Tfy}(=Bf|)i@55WFiA1uyskV_rUvC%)62+vGOZ2(iaV(=?`=9h4i z?*;&IyWxg2kkg1g8|G;|rnk<^9fye#_ zfxG_OWUyRgO}HF5=RZ#-%Nf>)6M@72=)ivRTH-wTcCmJB_wQj{-HmVP&VSoxd_@0m z+rsbLxO_Xlhda5=9{*nMyPwB66gcKT!ebr_ocEs!T=tV+fuG+!|JA@#|1H*^`+?8A zj^Df<-Tx6$)sH~DfIpBtV3KDc2lE8t@qUuwN18rZm-p5xP$<|vPzL|SYQZQxO6LYz z2iNd<><$bHo>(o0y~KVWfNXyD_c3eP<6-n2@F;2c|gKOMShn-UFY~@%lJspR3_Jp1>>S zDa=V-myJ}+J4+g+IWn4(do+M!VipL@HscZVXB91HD$ebtQBAdmdp!zY(N)YhPjke- zf~O*aB4;6z41p{#zYRf|?wr}G_|6S?LqX*>)Ta|`FX&+c9x(K8+=yCrnfyQpr@Q~N)r-u*!ZEP1zkQc%t3@je2V z`Qd3qXWo_SdnlQS)2XpnPe|i!&l= zoFT)2>mB1;07f#6OUL@)MEcgjx?Id>F#~_XvA)`T1{FzeE9z?vXS2R99gc;GNncQW zIejXLci+76d@uP_?|6TCFOq6`)O**v9lyd=FhA#bH+#o>7n8j(h3{slx4E}F9MR^a z=!Ju)kWT8&PG6J^^i0O_>#yexKX*O1aWOpOIfNp1Gu_T&68fg#M>dQEh92~PEj`uf z1dGB;%}g(t2(L1mN%ePdyr&>5G42&vr1Ao|lhb7fZCk7~3{6{q`4uYXhw!MbOU>mA|9DL`$lVdf18Hz z59&#u@MV0@H2N**&1*6lU!t{pj$Z2-$NW>+3s2Y{|Jy3_f817uts0kC=lUAlt`@hi z%l#T~|HeE{Gaj#%^bu_03v0%AoD_b+o{E+FOF7hM{S1+72hLCO^*Ne_#a=(nveiOLZe)&6>2IVW@91rDCaE@g3 zuNm+c%B_Upua^R!iLBrqg_Oqp)(*U*C;jeVkdLu+ywlhp7UH_GMp+4KU>i#Exa`0S z_-))F|NJHU%y)itMPs#-S=$nlgPxl8Ej!3wA@Gl~Zd{Mp`x@bw(GD&~PqgKO$vYp* zzB(QBV-b9YwWL+g+mn_{}^y zNIoot-(49p5UYB|dFp}*G-rM6z_HVlBWNJU(Fl~~6Hu1VKv}+!<7*|W=tgjb9iGpg z1GwlNLjiscpS~FIky|LqAAmzV_ap}wNsDh^Ch*Se-dI*z-BW-hn<5;oCBQLCgJ+Zn z+o%N2QN^1YTr{1x2AL`~af7YRkzI!j+q&L7Z28y ze^=zXa@?krw;-JI{QQ=ed*=4$6eSjqsfl&*Y2E%>BrgQb*%>0SW*HWRG( zFi>0BQ=d<}GOU*3p8M{co{QxF974;!5jM&KzM1hbQTn^@z@9&k!{Py)7T5Dl&vz%` z8VBF2ugn@A;-7Vn9QfV13oJvQIgzZ=2;~Rvs!!pVT!Le=ACAc?I3`n-Ps}ZDqgRcF z$-j=o-Dz?fa^V%cy?)3QL_e;RS{;9s5=0RrHSh+?8EEi!0%xWJfE!F z!OVl2xpq1wndhcvj%&fidWo~dStk=rYlTVn7rO_n!&=S(*oI3{UXDh2*$Jj$IN6nX z?PchAM&j<)4qaL$d`hz7Q{u7bqUaa`=F*nzg{syfD-SC7ICigYR$}Iw4egH4+GqZt zgSv|o{84l}t6&L7;jq!e%wg7pL0pu6Dm8tZ%e)2Oqij%%zJop_vmZRp^n+7&!W#6j&7zaRbxYX)pB&JxnUNjpqiAa zEB}DhywXxL>x&Oh&;`%oueJ*2-Yk0H(Nv%j_ye|w$JY>EUlsU%#c)o~sm<5Y;HsZc z8?Q-ts0Y>lc>eXo_qr2#@vXJ%%qGuM+a6PE(05nSwlTL{N4>iYCE|Q$nKRXV_-EzT z#xY+VNygU@W~>9$jLb1JXg$^RS~qwdo#~Z2vbASx%inFdyfxRi=60>QeH&(@ZMlCt z9;XA3*9q^fE^1M-(t@A zHn=uuP@~nk+F54BSD2gLQ@2x}AAlorl9}lx%~WrJSUmwRc(0|`e&U;`!^TnYEKH== zht1eV%c1vzdpVRD>LjhUzCdfPuP4`WuQmYA$QVA`889A~GRxis)A4|wfHVyln)H^> z^a{*V8{i|*5j1TO3Z;q6R2P$2x)ny_F+2^g=^e;-?1zGB42~|d;F7K~a)HK`#nYfZ zi36QrHV!eq#`(~2O(-^ZQ*70W=1;#b>>v&w2RSdZe&)w z59aP^^m5njOwJR0$v?vCjkO!W^XlXzz-=N88oM0MJf|oul8SKE>o_M-bY62hqsZ&$ zd~!xOevVLyV>gj&1kds{VKi zM8d%w0lRsOE2R+SN-IouWe}#hGPBlXqp!&!EO6z*TO%&RIxy^c*((75H*1NKDSthQ}!0plqt9k5|JO^wS9w$^->~izC0*`NyrX6ri z=e142_i+NRbu6!W1h0Jv?_mJ%r4PKi?!32-aAVq_G;fBdM*}>lYr-O}!tA@OYamL; z?xcgaAs@UU>agm#OO{3pTL7+17C17=;m3r)$+4WbI8i?4-&=6;E;*O+U^oU(brkVSs-USX&Qxpc(;q8?`jg|v_WJ-SX;^Ls<|M$rbL(lHU^HR5uqE6U? z3+obA)5&PuhQK}Q3bUojzYcRQo*@(LOt=6gvb*CC)DkD=I;^tg?Q8f19*57g4VUJn zmSRt_f~>Zm@E3YwHL8;`a(ltb@1&JFqV8E1wi;xTVkyIQbK64i+*&2?ed7x<%K-Y$Vj`uM4a9piJ zzp}unh%N+25|orZ>9X5^%GJYNu9ES`D2fM3PUDG@1_V7J$vTp87zA)TI+fKpMJ<3A zHr2SQkA@>Q&^U~yey83RH>gG^C2JUS^zz1J(5ulnJVol6@gzxQbcJ)#mUW^T9Gd#9 z^)*mCRtAADi(0Zch*lx}ww_170#IJVLJJ(6uW*A3%#LqWfWz*>w12a9C=8muSg|Dxut2b_V!ibr?>j`NN6 zN@pGOQh_BCDxn0a!Zl5mTkhiKZd~lKrp|3A0wnS6jj%zx>fA5aZX>V@P4?pxl_}&f?!^Pp;dKAjNu`uJuGx?st zHj!;Ie@|hX%5_t@%~WnX6(!76CgfAZN?cz74t-f}UmCqk3H19#@L4Yi3p%ejlx%>( z`05Sd74~Jy-jj;83rwL7Falch+MDto8c@O3!gH^x_+2Ou8=!>v5{ITZ=Jahm0%GtV zI}6(=TJWKB7sRcQ%hn1%VfB9yW(&`8^Sw*TK#VW|q^t+Z!ge_JHHKkb6P;lN)bK@7 z<>cbp^!O5l2`vN<--PL^fOBMF;T>nDN1RcvaV9xUV(9_=TsQM=E=LbPi!;VJSd)Wb zQ+DM{(SmbJZItolIFA%?wRL57)paGswZzT&LS@hS43p}qW8?An13#!2q!(Oge>(~O zw-a^zO4NxnKmbQlP4_|@-x9WH4bA{1P&{OZnUxGZj|@xeFRT9>7+bfQU7mrxwTG%; zHB7EqoDW99>gt1PzO~&Fc4{rV6m>;TD(;kadfS7}Cjhhc15UB`t+(hF?^BUn0;@bq zwY?ozz!g>`$6i`NiY_*&p=YIyHYi^L`Pp2PIx7>-No>~%S{qNdM*v4rt+~$#H}5%%wWDS zi+RON<{`TJ8zgRDL|u4-b><**jGfF=H&V~9Wc^tLHapw6!(8VoYV(V%R;Q`tqG8M( zM60sL=uU=hC)TZYu<2S++ch_qF&kQpHh(^q-yEY3)nIMr9&vttHQ-%_(-(vr6aIIL z;{WknKA!8NxLuS{m+R^q6ImfA(K$>3DV_$8eFpR0**FBwGkTCV)0Y);AUw`tOqj;7 zQci@oH{IC9wfmVn9Yw=*ic2oAeqQ0P+nm!L@!Lz>0zSe7{lULMlCm9~1ZZ*TVv?|u zrZ=;2HqFOpRsw~5WxAU>pwP|fbUK+s_#7wjdCoH@v7XLjJzY&&#CGb^BjyuwAikJ) zOpUeH$2XOVZ!9<8TxpyTYBSAlV-?36q#9lcP4N-yVyz?L;(#@gePJOr=_WL-htNr0 zLgV_7THzB;k{Zq-q3{LMqQ}W^kEc3ah)#B^-GwxbA@)ss8hqy!b`aHMsB;#V_)Wu;4q#=SL?ybMJ$X0P&3P)&ht33isFpZ_YX_>G(@t*JT_*M)oyM%FJzyY@ zqK=y9D#4mrhZVGg>ju8bZ*k2G;yn=x@|}SWwW#pKRYUlJf>IRv<1I2l$ivZILD<1@ zeG1LQ9gg?+_=>3T5#y1zlU}@nQ`HOBk3T|lSVlcz8;oS_nZ>%aMrd~zE%nB?bSQ}7c#y-Hpjbi||=CD6nf_Alr z>(fTrN4o4Gkg=nrP#kAF#denMJljPs|Bvf0bGsOBe}(&9<^ETBoU0&eS9#nkJbsK) zi`#^A+p65BBKIxJW0d5viohi*0N$FL=g0;&k`awgYOuDXu-+2FJ@JCPiKultWqayD>CD2gw79{8b|nOCGz z2Fi()p0ZnMFPm~x`45b^PjV$(Buk*a$_H+kSw0DGYCp`lEpSX%fIrTWzoA=w4fgT? zUF%iwmow;H4}!sL1Fu{q<(225i=7OvIYN?U{9)J&K7)(KVWl^u@@Py8GgrwjZA3x3 zSc=b{BCyB!ag+HB_v@Kh5BJLo(gm>)+}F%xS%-n!$oLZmI8T1$ocKtLKr7S{rD;=f z2WQlkoQ-F3h91KiyFYtwM>uJX;HOm){pjXDGxK?l%gY_Xi+)}cjtgI@5T4=oazj{3 zjWCN!A&TQ*sIZ8tVhT0IaH@>noa5S}Gio4|$Bj`lM4T>L_z{|M*?TSSl3^4 z%U{S+d4&heLlks3Ion;LH#!Li_z-z3yEx-*qKjI^F}jGVXSS<7ol;XSsZIS;iJq#2 zt2bxAE_93SK#*E+K5RtQRF|}@aE|xNbeHAmmP)$bJ4IYCoC0*7xm~wW16*-3(~V|e zeMp0nAf@XdYsDTX32R0ooE7Oq@l9WY&;JT^uS*@bYY_^G`Tx3F(KQ?W|4i1G>1@;3 zrm{`pvj4Y9{4cX9T<+m|FSiTf_VKu1Lb~F_JWf&`F9naAn#WJi^JK!KJsaL2xp@A3 zbjyW!J#ig#8D3vS*F}^JSE&YXI}LdKO?e-!c|RR_U)_0s5xmdAyx)<$?)?V)l!LK^yMKEKO+j<={O9+PSFhFgC{yAexGY0-DP@g7MeoO7iT zZn(0r2IfT_R-Bxhil`cE3Gt{IQ{kDAUFeB=d@wcT7@;1SH*IiI=qapb57axeR#sj|>ddT6_ zYg4EJ7qeP!;@<;kBhSHVI7XW>L+L30mHNsi^ArW{V*+`$oE#=>22_?gm|qrYzh5+sO-3ZkMQm`DnOTdewQ?pQinbyo4dN<31 zYi(VEHrakO^K4k-dJdhmz}mKptx>Sdyn8Fsy3)?R3me$M3MV!}hc%9s9WW#beIvO8<4ShHj^9>(V-ATa$L5 z*hX8kV&6_5w#>FL?Z#~L=)u`(*O=$8ezs|8muCCk8gY~4%a6AP_-AP)y+aZ2Qb5cq zhj&Z%jz`m86}Ngm%^lTCclFanJ#|oDcco1-3*e_|&Gksv>6z;5p)N^#MnBd~y>=9r zxg+gNdvm8Xq&I8JL9HUUxP%_>jI=fM;v#zSCwlQaainqDbX4jzDk$bx!}LcNbdYg(Z(7CdW#dwM8xj75Ii0#1r}Chzr*`T6)7wH$TeDklh7L5f z4%zkgG)!-weuX`k*;5;qUz43%4VrYm89*wUH@m#Ni!w^jQ(D3ds>R_8=O`(vqzD`N zY`@O(?JVQa3ma!unC*NP3;L||{PyLwC$D4kI4X~G^SDMHXXa&F=S?qX7OIMlt>T=j z&aL6>i=9=+8TB09zzkK_II@waXvS~3#aczTnMJoPi@cpD?&QfI@dl4OqL(B3IN~)3 zNPiak5O(^=^v>$$F?H0_xUlEbfAIcu>|J8-8l%Lvz?1hvk^h93Wu(uvPTT@DyMo2O z(F`ZMjGsSfrr30|$7JJw<%1^|5g921Q@((2R@)fGD`lEAG0#b>jG^WN{>XZ4Uq&;9 zzo2#BG=6O;ocUuTeY(9!wnKwrBy)}Vww#Mo0AkYuC8^)mNb;aj%1~bf!U1H$4=zSe^0)rLu-EG z+s|XsEHZz~a&o_#cfVfWyV?AA+s(qU3kI;)8NchXt?j9A{tl`<*;+zVpgU6`-qVs| zu9S(AlBYwSi;A>IY7C6Z=#*CS#pdoxxU%{!MIOy=Ew4&xYMq;EgC z;`2TqqwPA-cCF1E)Hvf#YdhYUQH@`0%_^hls+euAgwerg$nVMy>o`Vg_nDt=lhJm| zN%d^moZqLv&hzd~&b!Iw>>x^XTl&-V*CRAXJDQ|5?b0ODBL!%TY*qp|2H*JYM4|L8 z#(}PcZOk(kboz;1B9!Z`ceWHx@H2d4N`$%gmW9?Gp75ZOdz7?>Ni={gi9E(uo@xHw;wRS2WZh2n}p6(Hdk(f18Rt(%z-=MG%-fF9uS^!@scO!>okGDeCaegUhQ zYR4S|7+PX|2_&Fr9kz_6;J8x4|y&hmH53wO=&f z+5njVql{V~Zv^*r{@6mgV;vuE4}0jSQKZ?Rqi2W{mP~3PtMN{#c}H5Ot6x3os+Z`k zel+$Fdig^dYaC7e4gLEAojW(0gLDpWba~RYq>Wbl*-iKUN#DlIFnhXLl+Li7!viXl zT+&>o<&$giYcJ;sU2CSLX1=vDmt0$ZQU|{}^Od@r@APSC@C(WJB)c=7+WY6&A zuKeZ4cuP<4k$SQlpOGK&EMMsbmg9?T$(NzYubQW}FKhC3=<=K9?Rbk1)sNrZKY0V% z#OKV;i7zpLhS#WeF$#gf`2AC+5rz-k|RldenNp433BqK^5=E=*n`?mCy( zIYW$fviQYBXx!&&{$st?h@^w?@7?^`P3+-SVylbzyR)DIQ_NTL6?A;8wtincb&&Y* z8(RDYz0gyv=dR{4>6lbe+n;A|QJCKuBCR>~PpSI!6S(G2V|jk%`ETd#uNQ||A%49O zf;iiJ#M7(`^$lC$OTEw-GyRRkcSA&2-?rAmYnkuh)7Q+=_H1T%{Mp48W?Z-jC*RHn zzu73~rkM>OlXZMn&%Bh~auKVgs<|2}>YqxRd7wDV{jAKxqV2ztiydOH8${n%h$byE zYw~RKJWP-66@lHsBHAnpyABq=$~w2p$kL+No9v@k#Au(_pFPc9dR!#`k=WO4tO=sC z*khaZXGexOeqVf~(AvSfzn+Xjhb4S?UjZIn{K*hQ{ZgC|f`Z&<}@x)oiy zJoL4!+LR z3hrCU{m=It)x;Jqe<9=nxyyu-WRZN0`0`tXO$ zh4E-?jqHVu`tEJ+yvJSki&GpnYeu@aP01X~YMj8w`QBPJGtBZd&x#n!Wk#0sx z>ii1v?`vf?G>vAZE@XwrbG2YaEqNh5RF{o$tug8?#M1B3?j32KM@86sWqvJ^GMz4( zizAjnp4YQbcG6G>Xs8o3R5lU3eDqOKwoVzgWL1&;OV~nJ;mD?9`mO1s`|xBJzTT6z zK5WFdSW!b+RHMz8F@bG24Kh7f{=f>>*%s@7?1Ro6lXs9!B(Fenb@FvNMC}IArQ59i z-VxsRI2r9jX5WTck0jS$lJ{wTEr7;)R|KpP zkM`D-vqj~~KwqkgqFrhR>}$+JccU4w@8lQX4|(a9vQ%7dgBh##rVKPo)d$9-ex6dm z7~ON>jg`!LeX$v@8yI`q#4OdVjk&)sH7)gFt6x3E<9;!orjIdIgPx|6s!J2 z#Cliq7kbGL^9Zfzb9Ep`x5G1=>(?8a8MBUF z>H<<$TI}cS;91bM!`2^)nOnGh@i_a6VCgar&1;WuN8? zb%ot@)C0BQGu{M;y&fKW1zh%G_-qxuPbo9#6fxUPJ~Q29Puj)z++T1e9BSWN$v5nF+NLCeuH zwDcq`v44rGqS*lOK3ZCbi_hsJRg>+MyMoc^*whR6f?K&>6ze2}B?&={G_1|`j#F zK=N-iBVtQ<>uqGetvrr_q2GaPD~5_`1^ohkDY%`ptJlF5dF=ed)57^_VYsqi00i zp45XrmeP(syW88`p{Ko7r0phe+Eg6wdY=u|!sRkz>cE$4n&b5XTLoILl$9xp>6;6y z&3q{)JQtfwk+E_@jsK}v-)D5zc5Sjj+pMr&@FJG_Y*zX-eg4-`3%;u*-{g0^NCWny z6T52X`^B)^`gV(NjeV;xYFA4<=X{#56ka)-MW2U-kt%QHD7#}HdwdIRxDtZ2fHm?X ztK@68_^0fY5oVBmhkks8N7aiB^C-UUKtJ9=Ki-IouYrqQ%HpZU@+ph&i?D>k`W`_h zj#%sCS18bC*()n}baTx|HcbZ0SLA98O*ova^@nP|M9O;d1|MN_S$Btx{hE2sdXw_* za>F~ZuNx?*?iY&s+l{gTQGJZ7Za@9gC5?98?7&L!;7 z*=*A(?A-~h$xnE!BUqRNqq&=JXJ6mMyKMv=x{~dADUAHW2+wT7qHQR1p)Q2Hrn#T1 zK*`I*Vo>oTvLFvY#`jo3V5^LW^%;k(uedK`G301&#zrgHuY#E_iDpmw%9;}6WJiv% zvhpZ7X(Qyy4DoFMZ#>Mg^t#NnSLDs~$#_Yg+H)DbWLG>Xcl~j>72WvfkHDBZ!)-f3 zY45kLSBH$-n0w2b69&52vj5J?zQqW8rpj$Tb)#RJjQyL(%e~9?Q2pN1IsqIfQQp6=rk9}jt zwW%_oXKInT=2l;lQ9<5G)z}6jC%4P)+naG^>|n2uolmDpv`DyV~S!3h4y$1Iw9Hw0(uI1ml~C-Bdaqr$bARkYv(H`12Wemp&FgS(Giyw>jC!id`k)JO z_GS8@tMoyQar;f=;0_kReQbaSqnxghq+*P|YaEMUqSDDqf7EaNOjZ`cyqEJ^*C^S@ zTiI^jzrAE?zh8%-tS2(}>8<|cuN)VnPL>^WItwMQ6=Ms@!7j>YE=gX?S%0t!E2f66 z7VNgJKC6MxYuGf6Npf@FZeiox1_5pZBfp2tchHx0vTjco65mxctGj;f2|eA@@ZjE# zcs@eR+wptaTkoX<>C1{b|1PIs&hBG z%kgd4XLmUNHrHw8dbhZ43)gS%K26-Ok^5c?v2W-(>U*Ago~w@MtK~T_^t@H+tIFzYc3!CmDAg1b9Ry%IFVULPIgU}+yzjm3dXOL5}_}~1~@ZmDV;W- zEig-lz_iTcEV@5f5_?$`+t_mJ=(b;IvIT64Fc;bsNWw(z{F(M1rQL_h-0n}$y$VZs zmMs_TlrE65_LV=hoy4o{qma|*L$^0Mr*Dal|X9bz->i{)T1Wx@;&vBUQAn>WX1 zh^BlOTL4X%0T=j|-#v~W{vqFd2oL%#7>T#MVhIX7*uzo8@kXF9qFUHWa_rgD5tbI4E;=+D>p5YMzf@g7ye;& zs=Zd)*_vKV4te2><>~pw4s%$sDm7z@h}R_ZbB>b@JUac?^!KfSI|RD^wm9PJ=_}G- zv@ULM`D{N)K&G2bMy{n^g>mPYrn`!qxEEhUaN0u zjvHFbA-q@L_Mm8TcafB5@W?B8mgye>q;nWwPw;+4_Vf(fXxpaosk(z&`81 zA2tFZ31{YjN1b5~`eJaVvU={SRzIyRd%FS7ZVV&41)sNx^|bQSOSE`@-`*n!pOA!! zBw{+rm`6f_%xr;W?~5&@%~y#iZKcWg#ftJf%JMg=W#*=%3-h$j<3UuWpD*T#T|tVk z=bhb5vfDrm@8>Z*%9neZjK9pk>u=4o;n2p>e7~R*req%GKP1WW$t8>TOn78TI;9fL zToZ~^k5;~pUT#4j-9a0-r-!@nB%V_ED31kE*u7coID&}5qKCwAFF{^?42 zKpUgz;3sc|_0f~e<(6VTwKQHwc6RY;kbxYsx^t$S&i}{>)ygfKEl)~bmUG_7em=vuf~ZhR zervKe(el_@toBRRjP$mePD z!XK_aSfWeNls9>Ts|c9-yY*V)OU48pO-$`30@e zn-|v;GT%d7pqp62!{Pv)?CD_7y*$3Qq7=9B0$Lge&;p9sRE~NhrB^F$ptPPgt^;Mf zNH%-*PoI2K@XFpE~qnl=p;Spf1pkg_4Pl{>nG_A|KCJ!Ii{a; z`lGj<|BmYnaJ@mUJJ|JyxX&>68!jp}!u>~jj!|M&A4S;%y}jXcvLIg|xi5L^SE5XU zZ#-ehCYa)C(_LwXtIT$Vxz1joE*7hW(*_pl?6 z8$A+}gODl?a~i}eSMp97fWIc4DMnL7tDT#C!uYu)2tsx(olA@7*Ybt=hv(1_=kXQG zi+EMRH#O*p+9F={pbA&vqU-625C^*j-`!3lgs~{}K__gZ5BAUn2T0XXQWhh1*^EKS zO;XRWQg9LILrJqKmXl+6e#*s0gj~k6ZeX;?wQ%hwwidkaTj7dr_~G~Q#~fH&)yrV@FJ}HH6DLIzWqR%+rv^u$wvId+>K$CmPt^7?;#66q)bkk<#WEzCFYx3 zk+L{tow3oIWtHuKm+v({|9-i*hs@=8%(s|tDRR$FGdp80*}M6yQ(s6v+S#&si=`Gw zEn)SC(tefqtCC;V9JdxkIXR6LVdqsKtyQflQ$v>cMdnke1%0h!{)Nlr2VV|@t#4k2 zE9Ja3ltF%tITfyx|9HLp;l{GyZZLmCQ)|;SgWNW^BF&Acg?SxrI!QPCpGCL%9;h|C z6}56)OUK`WZgOr5*J$oqP2KSZTO-?b?sc_XyQ_TG_gT;9rJk#{C%(vYR`)+mYUnUpQc7qWa)((I;y7rP-FWb$h*|w7V}}QlLxxO{CjF9C#SPS>l)~{;w=e2%Mh|^>6 zmxtt`wu4Bu!7VNEOjG&L*Fat_r{ins1ulTPl%wI#rPa?&*@@rQ)9lM>^LhHGAEWrn zCVab+W?qQ1XVSUfE$wh=E~fe9%Tn@q8V1e1Ly1Yf1r83rq4d5Lq^gwgZM&y z`CZTP#U2-JddRrad--m+%4BK^du+&$sl$h<#^Wmoe>o>C9ibKXw zv3l}(YR9gE+*~2<`yx~)rxij^H~Uid*k%aN26)3dBcE3DfmWIcdpQhZDJyib+1?jfJ#<0F zQP~tnAU{V~tB2XFhj~PY=zh_RpdNdA$gmHJ!_HJCV0qJtDA1mC}|8% zX*fu^*l+SV4`fvKo)|N18H;7qjwQ32v#Gt)MP+kCyYj2Yg8ad=)b2S(>XcB& zrH$VyFFIRA?N?L(HMPVg#);K6YO+2Y7-R3 zhL}7CTX|AjKf^bD0dn$+_J0Ehyvtlc7uA{M?;=#YdU=E5h9N`tGLjsd!n`9UF$R#$AUmX5S+*vfU zEN-nJGExPUwD8IR!$ z5$5saZldu`-#F@f{=yVTPA886;rR)MFk1{_jx*-*EEYg~7CCc?i1ade&~nkE6|S%n z3bdLmuW_ZdXr0->)|2K9Xrn6!+60>jwE4fJa9o@=M(0j)jc~1S&5*G-!9B*i*O%`3 zg?o>qKf<$wXBz9-K7mM$HdnyMu&D6tA9#n6(Rviqz1!629pCi?gIslhtMzx4x19Zk zGhcJoE8hA=Ie{-Y`dPK|4D_U@qn@CPx;x@gHTj6WogrnN?CA(IxlerTZY|ITUUs{Z zTcIj9(^EJ4)s(JkBu;j%Z&zv6`dYWH&pJLY*6tVDs@f`xlU2YSWpGJJ2y-zStq82{ zOsGo%xLuyiw9K4xth0+kr9fX|kiX-2?l9i_0}uX=7x&`HosgKVGB-Bj;B|D|Dn90N zd9RD1(et4)bMW;~`1=QZK7}-V#|xbVhaOMsjg#LnmfjmpHiC?dAW_3e)?gAgfcEPT zi+qDTy#|+jnOFM)4frgax)+&yN=|nV9`9rHVOLVzg$Mi~slK0dx2Fm3C9`+S_ikeh z-t97VThos%X~Ubzb_-ZfGk9nd^4=KYb3NSWT8Q`6JnO6Ig9g?txq@z}XI8zDa?d%rsq5hnpMY%o|xKy27PHSDMZ@F3xUk@j40ynsk_PRyw zw^pxh)bBm|qxNuwPI8*NXoGHa-4oiR7hU%}P4*I9*O#vA4|@y9+c2#?QmcQgjX%Q$ z<73~`Y16g-PjuQG+d|tiT(U~G%lc?+?hbtPt6uYe6Q+4t%{nx;aj71wFiz9Ng#I1HL^%GJ4Qpy`YSJ#FYEWp?*7( zvzN%+8zlD~(maH;zYk>|O>RG@d%j{Zd{5qgpbKWhH5bA%mdmJKBaX9KKGjYU?tMl- z{h3)1mQfUDTGBcX<<0eRKCHPWl(~+Q`Ye%alr(|A-UMH64OzX5?b05?{2)B}Q8?}s zaNTEMy?r3FuSN0E?rfc&JfG+IOfU2Q-rx(q!$%sz+j*a*^iedLY(CFv2@7c@k7*tM zXEXn22UO%&-sSDWNAJQT@5)+yjAzsXuJRO5{b?vlZ~pvq?8ZKP`ukxSz zvM%3{-SZZkvOizy9l3=A-~fZ*FYmHGhoE7$;kNf|BW&-Zkv=~_qtJ&DjT9Y>)5m`Q zl}7j-=sh&tae;<8Zzvk#8t=N+U}q0<<^V^&WG}a4Mc*xs z9cuP=b$+Y5zZqV2Bk!{b-?WidxJGMSshzISUYD`5Ya7Q~lgC<34rdivoE5Zf8IjJC zBIU)j?%D8*Lhy@x@QYmVi_>sGs(g?byZbm^ISjQp0JYdBQ-2pe+J>Jtih+il(G|Su zrR?zqxNA;?T1>-%-`l>nO~8ZWc=BVRb06c@k-Yuk`1D=&`vAUtKghxBc=#2*eji-^ zEKK5QSi}=Bg2&(nkBECb1mC+~jH4X{@NU-ponkDviD|SXBQ3-;nu^6VCO6lz_Z!MI zt53S>vhwSYt6Fj(FBHqECZqGK9RQl zvX%c3|2ZVn=cxKSAs!S{pUI*~sbX%~jor*aBj(l;`DLgUlqFIa+FQizAH_tUN@%0f zwsN+Lw)3@NHBqXXCnIu~YVCR=>W?@iFCD~Q(NMa*0|+PTJ&x_b01CG z(YFVE>mri&D5UCfT=pbB>xJK*g+%qCSzp14eQDRXpl0v*HOQ}_e!U0F`T*yCgo8i9 z$Dh%*UqYU~f;@eL$G?{+H5JGINEiR4-f2l2s7swX|@^VA)K5w$aZa zlVuM)a34f)Kg{Y6a(0NM9p&?$P@17MiFQtbRA-~11GbohjOI?xq_I@W$Cth?%%xIvqEej{63`e1&hmfPj9ge;y5o9fh0T z!(&5exPdsWUxc+jjK4b3ejTFt@KboTdlWmqnci!TTN~rmYebN*#J}}KbT3g_Q^dI% zKCi5_oYM1@7FT*U*(fM#otMVTL3Xl{qa+e_0?u)mPCF3UB*{_c-Bz->f&N-eQiIM~ zOry;s(X+_@kEA{5i*F)3XC{AU8b9VcURtn$zL1MImap;=D=FAVLq%!_@=wCdTYbfO zUgDQL$NLEW-xIu#ZoH2!tgTMG#rC2^ck@N=k}&0>wtbmWix@9B=72DzCU`7+B`u*-NZOWCnYShP!cFpGKdffiX0bP>Nj&_cFu zoWArwL0|YDr$zoRQXXjWNm}BZFgkE)vrqF5KX{iPz0*u+(=0ahZ0|Y8`_5xcFHkFs_-RYj&@w*TFOaE~Z0pssh1RLF z4eD-_I^3!*x3ji)@%8q=686d4-w#hX7|XAI&rr{2s_(NhW1@q}+9pjKou;jFvc~hk z!}4pvg7AgHyvib4wYZ$jl3KX5wl2p)uf#S#U+Y(ApV!0*wfLu(!W!z~kp>WlhCJ5m z;BAfZPcvR@3;0{hXyju@yc9UAbA�#BH7Auy=t#bj6F^dBjf`75Oy&d{+E1j9hpb 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�gW*Gs1IwSz@jG9?PQM%u!Frfd+w@0Z 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 z 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 j|L5xK|H_&6;*|fSh5z3<=AZD=e)@jpJZY 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 OWU^GmxCI`wG{ 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 FvD* znH?p>I5sDQNl>w;3R2Y!L8h85$W?RM=CdtiTg0}6^Otg&r9zTw8Je4AB!Mgw!opG^ zz~&`~%OxxlvZ$f#!hC#>bGhHy-2Y4-$28$F+W3dUc#^tC3s;3-g!3rR{t)^LhlHPn z-Be&(g--B2+u}@a!L=I+GkEOd(WVU-DhU11_;eEr@(gkdP3e@?7PLZTLC(1mUNg5c znb$)D$va7Sq4c8hv@5SG-%{^Af)9F=74jK$_y?6sm0PI>SCSw$hZXY#G@*m35xXgy zP%HkZtg9>oN3?*lkP^GNk{6ZtDyYch{e6eO^KWX6tBP~D#P%z8DmK6eU#OT%1v(xT z+(1PaD$*vLsw#dUL#3F)rbvMtDJa~0l0L}4$p6AaeNld0egF>XdihrQT=a3HF)4V_Zz$5Q72lO zHUAg5lu5L3{A;;47-Hdk}NZ1-Atc1ede#pBY>l9L40L z4|Jyv!Tva@yD;}?5iA|7!^ER<&`Nct3g!()16F$W>fl!xQf~smz~g{}8AvMAkN3FO zU(oUYo67ATT%8*bs4n5xKFc)V4_3rS12b{9Pk}@=fhp^lz|O!(W+lS|+gUSjqt4q3 zgJ&Dg?rnhqoHvk3!N9;S&fCLOVLvm3!}#Hk2S!mDjtiVe4{|jyJ#ZU#&qEXW4%x6vpL_8$c;2kEpj{_x`H&kJ6Qy<<{Yj}IzgWBK_W;zptMPXf4 z#wFGuxEEH?8D4|Cyb7;_vxAwz)%0+72Th?f!9t1RS+=>d&|X*G}4`PPsvd@58X{1@Jq)KD*G5|hF?;U86^LT<$heGo^;UzrAP3+Ikp z2v;Lbq9gDB=!iM8B2tdjr&f`NRM{C!#njRL@SN^Nnnu$jgQ&z8L`z2Z&5Llb!uDj-u^+DcQyM`!9$pK^Vpb*bB6fZk3K>RkK~{K)snF^GR#V^2r9sn#q>2 zPdP2yDtk{)P>}m%<=6vtmN$cyJXXF}z70+NJ$X>>ljl?9WB=A%F#;C(dNjaSPy(ka z{0f6IpR%g5xv~eH$T_UC4=L}XA@-rN&npyQU)_{d^$6B_YlS<)MUD@jgk1DA8{&u` zPHNP8T=7>`+tF#>R$0VMRSnU^s<(=`MEprS#f0M>D_$ji(lY8Sv7_3ko{BGPr@FKH z0pw`Uw=}7%>N-q2`lFj(tSJU#x|!ysW~e5nS)wVZJ)&t$TEtK?9am^g+LPMy+E>~( zWN3`k=F=_L)<>h=U-y@G0Z9`FbXwg*T?w57Uz|zbPgg-dTh~s%4ZiLL-D>?S-5+|N z?y26S_vlOM&4woW@`gTW!l$AQU$0+eIIiDkxUauu_@e)0pz&!)fxBDQP}SJj(9YPC z{_=QOrArMvjJpgMjik;Q9~qp+RD%lUc7BuDSj|+_*v9muaiHmc#_6U$=sL&44-7Ybk!&^KQ`LTpJBxK(8()I|1&3>el{02jWJg?%{4bLtuwb{e$v}?#{3Jq z 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 DCvxi1 diff --git a/packages/opencode/src/cli/cmd/tui/component/logo.tsx b/packages/opencode/src/cli/cmd/tui/component/logo.tsx index e3e8074cd..557b86877 100644 --- a/packages/opencode/src/cli/cmd/tui/component/logo.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/logo.tsx @@ -2,7 +2,6 @@ import { BoxRenderable, MouseButton, MouseEvent, RGBA, TextAttributes } from "@o import { useRenderer } from "@opentui/solid" import { For, createMemo, createSignal, onCleanup, onMount, type JSX } from "solid-js" import { useTheme, tint } from "@tui/context/theme" -import * as Sound from "@tui/util/sound" import { go, logo } from "@/cli/logo" export type LogoShape = { @@ -563,7 +562,6 @@ export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } = const [now, setNow] = createSignal(0) let box: BoxRenderable | undefined let timer: ReturnType | undefined - let hum = false const stop = () => { if (!timer) return @@ -575,10 +573,6 @@ export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } = const t = performance.now() setNow(t) const item = hold() - if (item && !hum && t - item.at >= HOLD) { - hum = true - Sound.start() - } if (item && t - item.at >= CHARGE) { burst(item.x, item.y) } @@ -605,8 +599,6 @@ export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } = onCleanup(() => { stop() - hum = false - Sound.dispose() }) onMount(() => { @@ -626,14 +618,12 @@ export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } = setNow(t) if (!last) setRelease(undefined) setHold({ x, y, at: t, glyph: select(x, y, ctx) }) - hum = false start() } const burst = (x: number, y: number) => { const item = hold() if (!item) return - hum = false const t = performance.now() const age = t - item.at const rise = ramp(age, HOLD, CHARGE) @@ -655,7 +645,6 @@ export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } = ]) setNow(t) start() - Sound.pulse(lerp(0.8, 1, level)) } const frame = createMemo(() => { diff --git a/packages/opencode/src/cli/cmd/tui/util/sound.ts b/packages/opencode/src/cli/cmd/tui/util/sound.ts deleted file mode 100644 index df8b4dc2d..000000000 --- a/packages/opencode/src/cli/cmd/tui/util/sound.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Player } from "cli-sound" -import { mkdirSync } from "node:fs" -import { tmpdir } from "node:os" -import { basename, join } from "node:path" -import { Process } from "@/util/process" -import { which } from "@/util/which" -import pulseA from "../asset/pulse-a.wav" with { type: "file" } -import pulseB from "../asset/pulse-b.wav" with { type: "file" } -import pulseC from "../asset/pulse-c.wav" with { type: "file" } -import charge from "../asset/charge.wav" with { type: "file" } - -const FILE = [pulseA, pulseB, pulseC] - -const HUM = charge -const DIR = join(tmpdir(), "opencode-sfx") - -const LIST = [ - "ffplay", - "mpv", - "mpg123", - "mpg321", - "mplayer", - "afplay", - "play", - "omxplayer", - "aplay", - "cmdmp3", - "cvlc", - "powershell.exe", -] as const - -type Kind = (typeof LIST)[number] - -function args(kind: Kind, file: string, volume: number) { - if (kind === "ffplay") return [kind, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file] - if (kind === "mpv") - return [kind, "--no-video", "--audio-display=no", "--volume", String(Math.round(volume * 100)), file] - if (kind === "mpg123" || kind === "mpg321") return [kind, "-g", String(Math.round(volume * 100)), file] - if (kind === "mplayer") return [kind, "-vo", "null", "-volume", String(Math.round(volume * 100)), file] - if (kind === "afplay" || kind === "omxplayer" || kind === "aplay" || kind === "cmdmp3") return [kind, file] - if (kind === "play") return [kind, "-v", String(volume), file] - if (kind === "cvlc") return [kind, `--gain=${volume}`, "--play-and-exit", file] - return [kind, "-c", `(New-Object Media.SoundPlayer '${file.replace(/'/g, "''")}').PlaySync()`] -} - -let item: Player | null | undefined -let kind: Kind | null | undefined -let proc: Process.Child | undefined -let tail: ReturnType | undefined -let cache: Promise<{ hum: string; pulse: string[] }> | undefined -let seq = 0 -let shot = 0 - -function load() { - if (item !== undefined) return item - try { - item = new Player({ volume: 0.35 }) - } catch { - item = null - } - return item -} - -async function file(path: string) { - mkdirSync(DIR, { recursive: true }) - const next = join(DIR, basename(path)) - const out = Bun.file(next) - if (await out.exists()) return next - await Bun.write(out, Bun.file(path)) - return next -} - -function asset() { - cache ??= Promise.all([file(HUM), Promise.all(FILE.map(file))]).then(([hum, pulse]) => ({ hum, pulse })) - return cache -} - -function pick() { - if (kind !== undefined) return kind - kind = LIST.find((item) => which(item)) ?? null - return kind -} - -function run(file: string, volume: number) { - const kind = pick() - if (!kind) return - return Process.spawn(args(kind, file, volume), { - stdin: "ignore", - stdout: "ignore", - stderr: "ignore", - }) -} - -function clear() { - if (!tail) return - clearTimeout(tail) - tail = undefined -} - -function play(file: string, volume: number) { - const item = load() - if (!item) return run(file, volume)?.exited - return item.play(file, { volume }).catch(() => run(file, volume)?.exited) -} - -export function start() { - stop() - const id = ++seq - void asset().then(({ hum }) => { - if (id !== seq) return - const next = run(hum, 0.24) - if (!next) return - proc = next - void next.exited.then( - () => { - if (id !== seq) return - if (proc === next) proc = undefined - }, - () => { - if (id !== seq) return - if (proc === next) proc = undefined - }, - ) - }) -} - -export function stop(delay = 0) { - seq++ - clear() - if (!proc) return - const next = proc - if (delay <= 0) { - proc = undefined - void Process.stop(next).catch(() => undefined) - return - } - tail = setTimeout(() => { - tail = undefined - if (proc === next) proc = undefined - void Process.stop(next).catch(() => undefined) - }, delay) -} - -export function pulse(scale = 1) { - stop(140) - const index = shot++ % FILE.length - void asset() - .then(({ pulse }) => play(pulse[index], 0.26 + 0.14 * scale)) - .catch(() => undefined) -} - -export function dispose() { - stop() -} - -export * as Sound from "./sound" From d0b8ff0f222e37be9c9eed288c61f3b86d6e42c9 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 00:57:32 +0000 Subject: [PATCH 167/378] 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 ce8cded23..935e77f06 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-MUHog06sZEi6bXR1m8exdkjSNW9bHEv9bPQXACJ7SFw=", - "aarch64-linux": "sha256-3dwdZ3It++OsdGT8xMOQ10Arz8eeODp/LXOrI4DLEhY=", - "aarch64-darwin": "sha256-TmUPGDCewjsrT13npVH6B55J43NKKut67p/HgPJpQNM=", - "x86_64-darwin": "sha256-j8I7t3MZoUQUMFRWyaFO75TRbAw5TauSZAa4yKOHFMA=" + "x86_64-linux": "sha256-UCX1xw1DHJC/qM7dk2lD4gQbrtzg3LhwtRNQuwDVGH0=", + "aarch64-linux": "sha256-IgQmkCL+R48B15Kc+Fc9HzcKZeQgluEePwGNKL+QLqk=", + "aarch64-darwin": "sha256-7Kr4U/ueWVzSpOae4CsuVq5EwVfvc3bxOVkbdEkW0EU=", + "x86_64-darwin": "sha256-HyLQUa0D1gSSoMLO2gXraU3WTXEBnvHmjqY5B54Eg3w=" } } From b7c6fa611fb40bc12d7176c8bb092a816fdb1e99 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 20:59:02 -0400 Subject: [PATCH 168/378] effect: add RuntimeFlags service (#27181) --- packages/opencode/src/effect/runtime-flags.ts | 27 +++++++++ packages/opencode/src/plugin/index.ts | 15 +++-- .../agent/plugin-agent-regression.test.ts | 7 ++- .../test/effect/runtime-flags.test.ts | 55 +++++++++++++++++++ .../test/plugin/auth-override.test.ts | 2 + .../test/plugin/loader-shared.test.ts | 44 ++++----------- packages/opencode/test/plugin/trigger.test.ts | 7 ++- .../test/plugin/workspace-adapter.test.ts | 7 ++- packages/opencode/test/preload.ts | 1 - 9 files changed, 124 insertions(+), 41 deletions(-) create mode 100644 packages/opencode/src/effect/runtime-flags.ts create mode 100644 packages/opencode/test/effect/runtime-flags.test.ts diff --git a/packages/opencode/src/effect/runtime-flags.ts b/packages/opencode/src/effect/runtime-flags.ts new file mode 100644 index 000000000..5f07dc6ac --- /dev/null +++ b/packages/opencode/src/effect/runtime-flags.ts @@ -0,0 +1,27 @@ +import { Config, ConfigProvider, Context, Effect, Layer } from "effect" +import { ConfigService } from "@/effect/config-service" + +export class Service extends ConfigService.Service()("@opencode/RuntimeFlags", { + pure: Config.boolean("OPENCODE_PURE").pipe(Config.withDefault(false)), + disableDefaultPlugins: Config.boolean("OPENCODE_DISABLE_DEFAULT_PLUGINS").pipe(Config.withDefault(false)), +}) {} + +export type Info = Context.Service.Shape + +const emptyConfigLayer = Service.defaultLayer.pipe( + Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown({}))), + Layer.orDie, +) + +export const layer = (overrides: Partial = {}) => + Layer.effect( + Service, + Effect.gen(function* () { + const flags = yield* Service + return Service.of({ ...flags, ...overrides }) + }), + ).pipe(Layer.provide(emptyConfigLayer)) + +export const defaultLayer = Service.defaultLayer.pipe(Layer.orDie) + +export * as RuntimeFlags from "./runtime-flags" diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 68d47916c..e87f6db23 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -9,7 +9,6 @@ import { Config } from "@/config/config" import { Bus } from "../bus" import * as Log from "@opencode-ai/core/util/log" import { createOpencodeClient } from "@opencode-ai/sdk" -import { Flag } from "@opencode-ai/core/flag/flag" import { ServerAuth } from "@/server/auth" import { CodexAuthPlugin } from "./codex" import { Session } from "@/session/session" @@ -28,6 +27,7 @@ import { PluginLoader } from "./loader" import { parsePluginSpecifier, readPluginId, readV1Plugin, resolvePluginId } from "./shared" import { registerAdapter } from "@/control-plane/adapters" import type { WorkspaceAdapter } from "@/control-plane/types" +import { RuntimeFlags } from "@/effect/runtime-flags" const log = Log.create({ service: "plugin" }) @@ -112,6 +112,7 @@ export const layer = Layer.effect( Effect.gen(function* () { const bus = yield* Bus.Service const config = yield* Config.Service + const flags = yield* RuntimeFlags.Service const state = yield* InstanceState.make( Effect.fn("Plugin.state")(function* (ctx) { @@ -148,7 +149,7 @@ export const layer = Layer.effect( $: typeof Bun === "undefined" ? undefined : Bun.$, } - for (const plugin of INTERNAL_PLUGINS) { + for (const plugin of flags.disableDefaultPlugins ? [] : INTERNAL_PLUGINS) { log.info("loading internal plugin", { name: plugin.name }) const init = yield* Effect.tryPromise({ try: () => plugin(input), @@ -159,8 +160,8 @@ export const layer = Layer.effect( if (init._tag === "Some") hooks.push(init.value) } - const plugins = Flag.OPENCODE_PURE ? [] : (cfg.plugin_origins ?? []) - if (Flag.OPENCODE_PURE && cfg.plugin_origins?.length) { + const plugins = flags.pure ? [] : (cfg.plugin_origins ?? []) + if (flags.pure && cfg.plugin_origins?.length) { log.info("skipping external plugins in pure mode", { count: cfg.plugin_origins.length }) } if (plugins.length) yield* config.waitForDependencies() @@ -285,6 +286,10 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(Bus.layer), Layer.provide(Config.defaultLayer)) +export const defaultLayer = layer.pipe( + Layer.provide(Bus.layer), + Layer.provide(Config.defaultLayer), + Layer.provide(RuntimeFlags.defaultLayer), +) export * as Plugin from "." diff --git a/packages/opencode/test/agent/plugin-agent-regression.test.ts b/packages/opencode/test/agent/plugin-agent-regression.test.ts index e2dd8a5f7..60d59ee90 100644 --- a/packages/opencode/test/agent/plugin-agent-regression.test.ts +++ b/packages/opencode/test/agent/plugin-agent-regression.test.ts @@ -7,6 +7,7 @@ import { Agent } from "../../src/agent/agent" import { Bus } from "../../src/bus" import { Config } from "../../src/config/config" import { Env } from "../../src/env" +import { RuntimeFlags } from "../../src/effect/runtime-flags" import { Plugin } from "../../src/plugin" import { AccountTest } from "../fake/account" import { AuthTest } from "../fake/auth" @@ -29,7 +30,11 @@ const configLayer = Config.layer.pipe( Layer.provide(AccountTest.empty), Layer.provide(NpmTest.noop), ) -const pluginLayer = Plugin.layer.pipe(Layer.provide(Bus.layer), Layer.provide(configLayer)) +const pluginLayer = Plugin.layer.pipe( + Layer.provide(Bus.layer), + Layer.provide(configLayer), + Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })), +) const agentLayer = Agent.layer.pipe( Layer.provide(configLayer), Layer.provide(AuthTest.empty), diff --git a/packages/opencode/test/effect/runtime-flags.test.ts b/packages/opencode/test/effect/runtime-flags.test.ts new file mode 100644 index 000000000..5c9518a27 --- /dev/null +++ b/packages/opencode/test/effect/runtime-flags.test.ts @@ -0,0 +1,55 @@ +import { describe, expect } from "bun:test" +import { ConfigProvider, Effect, Layer } from "effect" +import { RuntimeFlags } from "../../src/effect/runtime-flags" +import { it } from "../lib/effect" + +const fromConfig = (input: Record) => + RuntimeFlags.defaultLayer.pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(input)))) + +const readFlags = RuntimeFlags.Service.useSync((flags) => flags) + +describe("RuntimeFlags", () => { + it.effect("defaultLayer parses plugin flags from the active ConfigProvider", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe( + Effect.provide( + fromConfig({ + OPENCODE_PURE: "true", + OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", + }), + ), + ) + + expect(flags.pure).toBe(true) + expect(flags.disableDefaultPlugins).toBe(true) + }), + ) + + it.effect("layer accepts partial test overrides and fills defaults from Config definitions", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe(Effect.provide(RuntimeFlags.layer({ disableDefaultPlugins: true }))) + + expect(flags.pure).toBe(false) + expect(flags.disableDefaultPlugins).toBe(true) + }), + ) + + it.effect("layer ignores the active ConfigProvider for omitted test overrides", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe( + Effect.provide(RuntimeFlags.layer()), + Effect.provide( + ConfigProvider.layer( + ConfigProvider.fromUnknown({ + OPENCODE_PURE: "true", + OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", + }), + ), + ), + ) + + expect(flags.pure).toBe(false) + expect(flags.disableDefaultPlugins).toBe(false) + }), + ) +}) diff --git a/packages/opencode/test/plugin/auth-override.test.ts b/packages/opencode/test/plugin/auth-override.test.ts index 402d755da..adc66e48c 100644 --- a/packages/opencode/test/plugin/auth-override.test.ts +++ b/packages/opencode/test/plugin/auth-override.test.ts @@ -7,6 +7,7 @@ import { provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" import { ProviderAuth } from "@/provider/auth" import { ProviderID } from "../../src/provider/schema" import { Plugin } from "@/plugin" +import { RuntimeFlags } from "@/effect/runtime-flags" import { Auth } from "@/auth" import { Bus } from "@/bus" import { TestConfig } from "../fixture/config" @@ -21,6 +22,7 @@ function layer(directory: string, plugins: string[]) { Layer.provide( Plugin.layer.pipe( Layer.provide(Bus.layer), + Layer.provide(RuntimeFlags.layer()), Layer.provide( TestConfig.layer({ get: () => diff --git a/packages/opencode/test/plugin/loader-shared.test.ts b/packages/opencode/test/plugin/loader-shared.test.ts index 1b6372390..6b1dd306d 100644 --- a/packages/opencode/test/plugin/loader-shared.test.ts +++ b/packages/opencode/test/plugin/loader-shared.test.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, describe, expect, spyOn } from "bun:test" +import { afterEach, describe, expect, spyOn } from "bun:test" import { Effect, Layer } from "effect" import fs from "fs/promises" import path from "path" @@ -8,23 +8,13 @@ import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/f import { testEffect } from "../lib/effect" import { Filesystem } from "@/util/filesystem" -const disableDefault = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS -process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" - const { Plugin } = await import("../../src/plugin/index") const { PluginLoader } = await import("../../src/plugin/loader") const { readPackageThemes } = await import("../../src/plugin/shared") const { Bus } = await import("../../src/bus") const { Npm } = await import("@opencode-ai/core/npm") const { TestConfig } = await import("../fixture/config") - -afterAll(() => { - if (disableDefault === undefined) { - delete process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS - return - } - process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = disableDefault -}) +const { RuntimeFlags } = await import("../../src/effect/runtime-flags") afterEach(async () => { await disposeAllInstances() @@ -43,7 +33,7 @@ function withTmp( }) } -function load(dir: string) { +function load(dir: string, flags?: Parameters[0]) { const source = path.join(dir, "opencode.json") return Effect.gen(function* () { const config = yield* Effect.promise( @@ -57,6 +47,7 @@ function load(dir: string) { Effect.provide( Plugin.layer.pipe( Layer.provide(Bus.layer), + Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true, ...flags })), Layer.provide( TestConfig.layer({ get: () => @@ -934,25 +925,14 @@ export default { }, (tmp) => Effect.gen(function* () { - const pure = process.env.OPENCODE_PURE - process.env.OPENCODE_PURE = "1" - - try { - yield* load(tmp.path) - const called = yield* Effect.promise(() => - fs - .readFile(tmp.extra.mark, "utf8") - .then(() => true) - .catch(() => false), - ) - expect(called).toBe(false) - } finally { - if (pure === undefined) { - delete process.env.OPENCODE_PURE - } else { - process.env.OPENCODE_PURE = pure - } - } + yield* load(tmp.path, { pure: true }) + const called = yield* Effect.promise(() => + fs + .readFile(tmp.extra.mark, "utf8") + .then(() => true) + .catch(() => false), + ) + expect(called).toBe(false) }), ), ) diff --git a/packages/opencode/test/plugin/trigger.test.ts b/packages/opencode/test/plugin/trigger.test.ts index 18fe0e82e..94642fba6 100644 --- a/packages/opencode/test/plugin/trigger.test.ts +++ b/packages/opencode/test/plugin/trigger.test.ts @@ -10,6 +10,7 @@ import { Auth } from "../../src/auth" import { Bus } from "../../src/bus" import { Config } from "../../src/config/config" import { Env } from "../../src/env" +import { RuntimeFlags } from "../../src/effect/runtime-flags" import { Plugin } from "../../src/plugin/index" import { ModelID, ProviderID } from "../../src/provider/schema" import { provideTmpdirInstance } from "../fixture/fixture" @@ -33,7 +34,11 @@ const configLayer = Config.layer.pipe( ) const it = testEffect( Layer.mergeAll( - Plugin.layer.pipe(Layer.provide(Bus.layer), Layer.provide(configLayer)), + Plugin.layer.pipe( + Layer.provide(Bus.layer), + Layer.provide(configLayer), + Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })), + ), CrossSpawnSpawner.defaultLayer, ), ) diff --git a/packages/opencode/test/plugin/workspace-adapter.test.ts b/packages/opencode/test/plugin/workspace-adapter.test.ts index d4aaae4a9..bef860432 100644 --- a/packages/opencode/test/plugin/workspace-adapter.test.ts +++ b/packages/opencode/test/plugin/workspace-adapter.test.ts @@ -11,6 +11,7 @@ import { Auth } from "../../src/auth" import { Bus } from "../../src/bus" import { Config } from "../../src/config/config" import { Env } from "../../src/env" +import { RuntimeFlags } from "../../src/effect/runtime-flags" import { Workspace } from "../../src/control-plane/workspace" import { Plugin } from "../../src/plugin/index" import { InstanceBootstrap } from "../../src/project/bootstrap-service" @@ -35,7 +36,11 @@ const configLayer = Config.layer.pipe( Layer.provide(emptyAccount), Layer.provide(NpmTest.noop), ) -const pluginLayer = Plugin.layer.pipe(Layer.provide(Bus.layer), Layer.provide(configLayer)) +const pluginLayer = Plugin.layer.pipe( + Layer.provide(Bus.layer), + Layer.provide(configLayer), + Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })), +) const noopBootstrapLayer = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) const workspaceLayer = Workspace.defaultLayer.pipe( Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrapLayer))), diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts index b408f7ef1..6447c2fe9 100644 --- a/packages/opencode/test/preload.ts +++ b/packages/opencode/test/preload.ts @@ -45,7 +45,6 @@ process.env["OPENCODE_TEST_HOME"] = testHome // Set test managed config directory to isolate tests from system managed settings const testManagedConfigDir = path.join(dir, "managed") process.env["OPENCODE_TEST_MANAGED_CONFIG_DIR"] = testManagedConfigDir -process.env["OPENCODE_DISABLE_DEFAULT_PLUGINS"] = "true" // Write the cache version file to prevent global/index.ts from clearing the cache const cacheDir = path.join(dir, "cache", "opencode") From 31e2d72bf7634bb7089cfc8f73300a27b0134100 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 21:09:58 -0400 Subject: [PATCH 169/378] test(worktree): scope created worktree cleanup (#27191) --- .../opencode/test/project/worktree.test.ts | 93 +++++++++---------- 1 file changed, 45 insertions(+), 48 deletions(-) diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index 3975db1ae..1de760014 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -38,12 +38,31 @@ const waitReady = Effect.fn("WorktreeTest.waitReady")(function* () { ) }) -const disposeWorktreeInstance = (directory: string) => +const removeCreatedWorktree = (directory: string) => Effect.gen(function* () { + const svc = yield* Worktree.Service const ctx = yield* Effect.sync(() => Instance.current).pipe(provideInstance(directory)) yield* Effect.promise(() => InstanceRuntime.disposeInstance(ctx)) + const ok = yield* svc.remove({ directory }) + if (!ok) return yield* Effect.fail(new Error(`failed to remove worktree ${directory}`)) }) +const withCreatedWorktree = ( + input: Parameters[0], + use: (created: { info: Worktree.Info; ready: { name: string; branch?: string } }) => Effect.Effect, +) => + Effect.acquireUseRelease( + Effect.gen(function* () { + const svc = yield* Worktree.Service + const ready = yield* waitReady().pipe(Effect.forkScoped) + const info = yield* svc.create(input) + const props = yield* Fiber.join(ready) + return { info, ready: props } + }), + use, + ({ info }) => removeCreatedWorktree(info.directory), + ) + const git = Effect.fn("WorktreeTest.git")(function* (cwd: string, args: string[]) { const service = yield* Git.Service const result = yield* service.run(args, { cwd }) @@ -158,66 +177,45 @@ describe("Worktree", () => { it.instance( "create returns worktree info and remove cleans up", () => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const ready = yield* waitReady().pipe(Effect.forkScoped) - const info = yield* svc.create() - - expect(info.name).toBeDefined() - expect(info.branch ?? "").toStartWith("opencode/") - expect(info.directory).toBeDefined() - - yield* Fiber.join(ready) - yield* disposeWorktreeInstance(info.directory) - - const ok = yield* svc.remove({ directory: info.directory }) - expect(ok).toBe(true) - }), + withCreatedWorktree(undefined, ({ info }) => + Effect.gen(function* () { + expect(info.name).toBeDefined() + expect(info.branch ?? "").toStartWith("opencode/") + expect(info.directory).toBeDefined() + }), + ), { git: true }, ) it.instance( "create returns after setup and fires Event.Ready after bootstrap", () => - Effect.gen(function* () { - const test = yield* TestInstance - const fs = yield* AppFileSystem.Service - const svc = yield* Worktree.Service - const ready = yield* waitReady().pipe(Effect.forkScoped) - const info = yield* svc.create() - - expect(info.name).toBeDefined() - expect(info.branch ?? "").toStartWith("opencode/") + withCreatedWorktree(undefined, ({ info, ready }) => + Effect.gen(function* () { + const svc = yield* Worktree.Service - const text = yield* git(test.directory, ["worktree", "list", "--porcelain"]) - const next = yield* fs.realPath(info.directory).pipe(Effect.catch(() => Effect.succeed(info.directory))) - expect(normalize(text)).toContain(normalize(next)) + expect(info.name).toBeDefined() + expect(info.branch ?? "").toStartWith("opencode/") - const props = yield* Fiber.join(ready) - expect(props.name).toBe(info.name) - expect(props.branch).toBe(info.branch) + expect(ready.name).toBe(info.name) + expect(ready.branch).toBe(info.branch) - yield* disposeWorktreeInstance(info.directory) - yield* svc.remove({ directory: info.directory }) - }), + const list = yield* svc.list() + expect(list).toContainEqual(expect.objectContaining({ name: info.name, branch: info.branch })) + }), + ), { git: true }, ) it.instance( "create with custom name", () => - Effect.gen(function* () { - const svc = yield* Worktree.Service - const ready = yield* waitReady().pipe(Effect.forkScoped) - const info = yield* svc.create({ name: "test-workspace" }) - - expect(info.name).toBe("test-workspace") - expect(info.branch).toBe("opencode/test-workspace") - - yield* Fiber.join(ready) - yield* disposeWorktreeInstance(info.directory) - yield* svc.remove({ directory: info.directory }) - }), + withCreatedWorktree({ name: "test-workspace" }, ({ info }) => + Effect.gen(function* () { + expect(info.name).toBe("test-workspace") + expect(info.branch).toBe("opencode/test-workspace") + }), + ), { git: true }, ) }) @@ -239,8 +237,7 @@ describe("Worktree", () => { expect(normalizedList).toContain(normalizedDir) yield* Fiber.join(ready) - yield* disposeWorktreeInstance(info.directory) - yield* svc.remove({ directory: info.directory }) + yield* removeCreatedWorktree(info.directory) }), { git: true }, ) From b6d3fa09bc001d9bee624f03702ac573329ba6e9 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 12 May 2026 21:24:53 -0400 Subject: [PATCH 170/378] effect(core): add AppProcess service (Phase 1) (#27178) --- packages/core/src/process.ts | 207 ++++++++++++++++++++ packages/core/test/process/process.test.ts | 210 +++++++++++++++++++++ 2 files changed, 417 insertions(+) create mode 100644 packages/core/src/process.ts create mode 100644 packages/core/test/process/process.test.ts diff --git a/packages/core/src/process.ts b/packages/core/src/process.ts new file mode 100644 index 000000000..f855459b2 --- /dev/null +++ b/packages/core/src/process.ts @@ -0,0 +1,207 @@ +import { Context, Duration, Effect, Fiber, Layer, Schema, Stream } from "effect" +import type { PlatformError } from "effect/PlatformError" +import { ChildProcess } from "effect/unstable/process" +import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" +import { CrossSpawnSpawner } from "./cross-spawn-spawner" + +export class AppProcessError extends Schema.TaggedErrorClass()("AppProcessError", { + command: Schema.String, + exitCode: Schema.optional(Schema.Number), + stderr: Schema.optional(Schema.String), + cause: Schema.optional(Schema.Defect), +}) {} + +export interface RunOptions { + readonly maxOutputBytes?: number + readonly maxErrorBytes?: number + readonly signal?: AbortSignal + readonly timeout?: Duration.Input +} + +export interface RunStreamOptions { + readonly signal?: AbortSignal + readonly includeStderr?: boolean + readonly okExitCodes?: ReadonlyArray + readonly maxErrorBytes?: number +} + +export interface RunResult { + readonly command: string + readonly exitCode: number + readonly stdout: Buffer + readonly stderr: Buffer + readonly truncated: boolean +} + +export type Interface = ChildProcessSpawner["Service"] & { + readonly run: (command: ChildProcess.Command, options?: RunOptions) => Effect.Effect + readonly runStream: ( + command: ChildProcess.Command, + options?: RunStreamOptions, + ) => Stream.Stream +} + +export class Service extends Context.Service()("@opencode/AppProcess") {} + +export const requireSuccess = (result: RunResult): Effect.Effect => + result.exitCode === 0 + ? Effect.succeed(result) + : Effect.fail( + new AppProcessError({ + command: result.command, + exitCode: result.exitCode, + stderr: result.stderr.toString("utf8"), + }), + ) + +export const requireExitIn = + (codes: ReadonlyArray) => + (result: RunResult): Effect.Effect => + codes.includes(result.exitCode) + ? Effect.succeed(result) + : Effect.fail( + new AppProcessError({ + command: result.command, + exitCode: result.exitCode, + stderr: result.stderr.toString("utf8"), + }), + ) + +const describeCommand = (command: ChildProcess.Command): string => { + if (command._tag === "StandardCommand") { + return command.args.length ? `${command.command} ${command.args.join(" ")}` : command.command + } + return `${describeCommand(command.left)} | ${describeCommand(command.right)}` +} + +const wrapError = (description: string, cause: unknown): AppProcessError => + cause instanceof AppProcessError ? cause : new AppProcessError({ command: description, cause }) + +const abortError = (signal: AbortSignal): Error => { + const reason = signal.reason + if (reason instanceof Error) return reason + const err = new Error("Aborted") + err.name = "AbortError" + return err +} + +const waitForAbort = (signal: AbortSignal) => + Effect.callback((resume) => { + if (signal.aborted) { + resume(Effect.fail(abortError(signal))) + return + } + const onabort = () => resume(Effect.fail(abortError(signal))) + signal.addEventListener("abort", onabort, { once: true }) + return Effect.sync(() => signal.removeEventListener("abort", onabort)) + }) + +const collectStream = (stream: Stream.Stream, maxOutputBytes: number | undefined) => + Stream.runFold( + stream, + () => ({ chunks: [] as Uint8Array[], bytes: 0, truncated: false }), + (acc, chunk) => { + if (maxOutputBytes === undefined) { + acc.chunks.push(chunk) + acc.bytes += chunk.length + return acc + } + const remaining = maxOutputBytes - acc.bytes + if (remaining > 0) acc.chunks.push(remaining >= chunk.length ? chunk : chunk.slice(0, remaining)) + acc.bytes += chunk.length + acc.truncated = acc.truncated || acc.bytes > maxOutputBytes + return acc + }, + ).pipe(Effect.map((x) => ({ buffer: Buffer.concat(x.chunks), truncated: x.truncated }))) + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const spawner = yield* ChildProcessSpawner + + const run = Effect.fn("AppProcess.run")(function* (command: ChildProcess.Command, options?: RunOptions) { + const description = describeCommand(command) + const collect = Effect.scoped( + Effect.gen(function* () { + const handle = yield* spawner.spawn(command) + const [stdout, stderr, exitCode] = yield* Effect.all( + [ + collectStream(handle.stdout, options?.maxOutputBytes), + collectStream(handle.stderr, options?.maxErrorBytes), + handle.exitCode, + ], + { concurrency: "unbounded" }, + ) + return { + command: description, + exitCode, + stdout: stdout.buffer, + stderr: stderr.buffer, + truncated: stdout.truncated, + } satisfies RunResult + }), + ) + const timed = options?.timeout + ? Effect.timeoutOrElse(collect, { + duration: options.timeout, + orElse: () => + Effect.fail(new AppProcessError({ command: description, cause: new Error("Timed out") })), + }) + : collect + const aborted = options?.signal + ? timed.pipe( + Effect.raceFirst( + waitForAbort(options.signal).pipe(Effect.mapError((cause) => wrapError(description, cause))), + ), + ) + : timed + return yield* aborted.pipe(Effect.catch((cause) => Effect.fail(wrapError(description, cause)))) + }) + + const runStream = (command: ChildProcess.Command, options?: RunStreamOptions): Stream.Stream => { + const description = describeCommand(command) + const okExitCodes = options?.okExitCodes + const built: Stream.Stream = Stream.unwrap( + Effect.gen(function* () { + const handle = yield* spawner.spawn(command) + const stderrFiber = yield* Effect.forkScoped( + collectStream(handle.stderr, options?.maxErrorBytes).pipe( + Effect.map((x) => x.buffer.toString("utf8")), + ), + ) + const source = options?.includeStderr === true ? handle.all : handle.stdout + const lines = source.pipe( + Stream.decodeText, + Stream.splitLines, + Stream.filter((line) => line.length > 0), + ) + const tail = Stream.unwrap( + Effect.gen(function* () { + const code = yield* handle.exitCode + if (okExitCodes && okExitCodes.length > 0 && !okExitCodes.includes(code)) { + const stderr = yield* Fiber.join(stderrFiber) + return Stream.fail(new AppProcessError({ command: description, exitCode: code, stderr })) + } + return Stream.empty + }), + ) + return Stream.concat(lines, tail) as Stream.Stream + }), + ) + const mapped = built.pipe( + Stream.catch((cause): Stream.Stream => Stream.fail(wrapError(description, cause))), + ) + if (!options?.signal) return mapped + const signal = options.signal + return mapped.pipe( + Stream.interruptWhen(waitForAbort(signal).pipe(Effect.mapError((cause) => wrapError(description, cause)))), + ) + } + + return Service.of({ ...spawner, run, runStream }) + }), +) + +export const defaultLayer = layer.pipe(Layer.provide(CrossSpawnSpawner.defaultLayer)) + +export * as AppProcess from "./process" diff --git a/packages/core/test/process/process.test.ts b/packages/core/test/process/process.test.ts new file mode 100644 index 000000000..1b46c1f1e --- /dev/null +++ b/packages/core/test/process/process.test.ts @@ -0,0 +1,210 @@ +import { describe, expect } from "bun:test" +import { Effect, Exit, Stream } from "effect" +import { ChildProcess } from "effect/unstable/process" +import { AppProcess } from "@opencode-ai/core/process" +import { testEffect } from "../lib/effect" + +const it = testEffect(AppProcess.defaultLayer) + +const NODE = process.execPath +const cmd = (...args: string[]) => ChildProcess.make(NODE, args) + +describe("AppProcess", () => { + describe("run", () => { + it.effect( + "captures stdout and exit code zero", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc.run(cmd("-e", "process.stdout.write('hi\\n')")) + expect(result.exitCode).toBe(0) + expect(result.stdout.toString("utf8")).toBe("hi\n") + expect(result.truncated).toBe(false) + }), + ) + + it.effect( + "non-zero exit returns RunResult; caller can require success", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc.run(cmd("-e", "process.exit(1)")) + expect(result.exitCode).toBe(1) + }), + ) + + it.effect( + "requireSuccess fails on non-zero exit", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const exit = yield* Effect.exit(svc.run(cmd("-e", "process.exit(1)")).pipe(Effect.flatMap(AppProcess.requireSuccess))) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + const reason = exit.cause.reasons[0] + if (reason && reason._tag === "Fail") { + expect(reason.error).toBeInstanceOf(AppProcess.AppProcessError) + expect((reason.error as AppProcess.AppProcessError).exitCode).toBe(1) + } else { + throw new Error("expected fail reason") + } + } + }), + ) + + it.effect( + "requireSuccess succeeds on exit 0", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc.run(cmd("-e", "process.exit(0)")).pipe(Effect.flatMap(AppProcess.requireSuccess)) + expect(result.exitCode).toBe(0) + }), + ) + + it.effect( + "requireExitIn allowlists multiple exit codes", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const requireZeroOrOne = AppProcess.requireExitIn([0, 1]) + const okZero = yield* svc.run(cmd("-e", "process.exit(0)")).pipe(Effect.flatMap(requireZeroOrOne)) + expect(okZero.exitCode).toBe(0) + const okOne = yield* svc.run(cmd("-e", "process.exit(1)")).pipe(Effect.flatMap(requireZeroOrOne)) + expect(okOne.exitCode).toBe(1) + const exit = yield* Effect.exit( + svc.run(cmd("-e", "process.exit(2)")).pipe(Effect.flatMap(requireZeroOrOne)), + ) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + const reason = exit.cause.reasons[0] + if (reason && reason._tag === "Fail") { + expect(reason.error).toBeInstanceOf(AppProcess.AppProcessError) + expect((reason.error as AppProcess.AppProcessError).exitCode).toBe(2) + } + } + }), + ) + + it.effect( + "truncates output when maxOutputBytes is set", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc.run(cmd("-e", "process.stdout.write('0123456789')"), { maxOutputBytes: 5 }) + expect(result.exitCode).toBe(0) + expect(result.truncated).toBe(true) + expect(result.stdout.length).toBe(5) + expect(result.stdout.toString("utf8")).toBe("01234") + }), + ) + + it.effect( + "result includes command description", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc.run(cmd("-e", "process.stdout.write('hi')")) + expect(result.command).toBe(`${NODE} -e process.stdout.write('hi')`) + }), + ) + }) + + describe("inherited platform methods", () => { + it.effect( + "string returns stdout as string", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const out = yield* svc.string(cmd("-e", "process.stdout.write('hi\\n')")) + expect(out).toBe("hi\n") + }), + ) + + it.effect( + "lines returns the platform's array of lines", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const out = yield* svc.lines(cmd("-e", "process.stdout.write('a\\nb\\n')")) + expect(Array.from(out)).toEqual(["a", "b"]) + }), + ) + }) + + describe("runStream", () => { + it.live( + "emits lines incrementally and ends cleanly on exit 0", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc + .runStream(cmd("-e", "console.log('one'); console.log('two'); console.log('three')")) + .pipe(Stream.runCollect) + expect(Array.from(result)).toEqual(["one", "two", "three"]) + }), + ) + + it.live( + "fails with AppProcessError when exit not in okExitCodes", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const exit = yield* Effect.exit( + svc + .runStream(cmd("-e", "console.log('a'); process.exit(2)"), { okExitCodes: [0] }) + .pipe(Stream.runCollect), + ) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + const reason = exit.cause.reasons[0] + if (reason && reason._tag === "Fail") { + expect(reason.error).toBeInstanceOf(AppProcess.AppProcessError) + } + } + }), + ) + + it.live( + "okExitCodes allowlist treats non-zero as success", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc + .runStream(cmd("-e", "console.log('only'); process.exit(1)"), { okExitCodes: [0, 1] }) + .pipe(Stream.runCollect) + expect(Array.from(result)).toEqual(["only"]) + }), + ) + + it.live( + "without okExitCodes, never fails on exit code", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const result = yield* svc + .runStream(cmd("-e", "console.log('only'); process.exit(7)")) + .pipe(Stream.runCollect) + expect(Array.from(result)).toEqual(["only"]) + }), + ) + + it.live( + "AbortSignal interrupts the stream", + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const controller = new AbortController() + setTimeout(() => controller.abort(), 50) + const exit = yield* Effect.exit( + svc + .runStream(cmd("-e", "setInterval(() => console.log('tick'), 100); setTimeout(() => {}, 60_000)"), { + signal: controller.signal, + }) + .pipe(Stream.runCollect), + ) + expect(Exit.isFailure(exit)).toBe(true) + }), + ) + }) + + describe("spawn (inherited)", () => { + it.live( + "returns the platform ChildProcessHandle for advanced use", + Effect.scoped( + Effect.gen(function* () { + const svc = yield* AppProcess.Service + const handle = yield* svc.spawn(cmd("-e", "setInterval(() => {}, 1_000)")) + expect(yield* handle.isRunning).toBe(true) + yield* handle.kill() + }), + ), + ) + }) +}) From adccab597068be3aed3f0d661bbbfba74529ccf3 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 13 May 2026 01:26:12 +0000 Subject: [PATCH 171/378] chore: generate --- packages/core/src/process.ts | 12 ++++++------ packages/core/test/process/process.test.ts | 16 ++++++---------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/core/src/process.ts b/packages/core/src/process.ts index f855459b2..2da8eb834 100644 --- a/packages/core/src/process.ts +++ b/packages/core/src/process.ts @@ -144,8 +144,7 @@ export const layer = Layer.effect( const timed = options?.timeout ? Effect.timeoutOrElse(collect, { duration: options.timeout, - orElse: () => - Effect.fail(new AppProcessError({ command: description, cause: new Error("Timed out") })), + orElse: () => Effect.fail(new AppProcessError({ command: description, cause: new Error("Timed out") })), }) : collect const aborted = options?.signal @@ -158,16 +157,17 @@ export const layer = Layer.effect( return yield* aborted.pipe(Effect.catch((cause) => Effect.fail(wrapError(description, cause)))) }) - const runStream = (command: ChildProcess.Command, options?: RunStreamOptions): Stream.Stream => { + const runStream = ( + command: ChildProcess.Command, + options?: RunStreamOptions, + ): Stream.Stream => { const description = describeCommand(command) const okExitCodes = options?.okExitCodes const built: Stream.Stream = Stream.unwrap( Effect.gen(function* () { const handle = yield* spawner.spawn(command) const stderrFiber = yield* Effect.forkScoped( - collectStream(handle.stderr, options?.maxErrorBytes).pipe( - Effect.map((x) => x.buffer.toString("utf8")), - ), + collectStream(handle.stderr, options?.maxErrorBytes).pipe(Effect.map((x) => x.buffer.toString("utf8"))), ) const source = options?.includeStderr === true ? handle.all : handle.stdout const lines = source.pipe( diff --git a/packages/core/test/process/process.test.ts b/packages/core/test/process/process.test.ts index 1b46c1f1e..726c3c4d8 100644 --- a/packages/core/test/process/process.test.ts +++ b/packages/core/test/process/process.test.ts @@ -35,7 +35,9 @@ describe("AppProcess", () => { "requireSuccess fails on non-zero exit", Effect.gen(function* () { const svc = yield* AppProcess.Service - const exit = yield* Effect.exit(svc.run(cmd("-e", "process.exit(1)")).pipe(Effect.flatMap(AppProcess.requireSuccess))) + const exit = yield* Effect.exit( + svc.run(cmd("-e", "process.exit(1)")).pipe(Effect.flatMap(AppProcess.requireSuccess)), + ) expect(Exit.isFailure(exit)).toBe(true) if (Exit.isFailure(exit)) { const reason = exit.cause.reasons[0] @@ -67,9 +69,7 @@ describe("AppProcess", () => { expect(okZero.exitCode).toBe(0) const okOne = yield* svc.run(cmd("-e", "process.exit(1)")).pipe(Effect.flatMap(requireZeroOrOne)) expect(okOne.exitCode).toBe(1) - const exit = yield* Effect.exit( - svc.run(cmd("-e", "process.exit(2)")).pipe(Effect.flatMap(requireZeroOrOne)), - ) + const exit = yield* Effect.exit(svc.run(cmd("-e", "process.exit(2)")).pipe(Effect.flatMap(requireZeroOrOne))) expect(Exit.isFailure(exit)).toBe(true) if (Exit.isFailure(exit)) { const reason = exit.cause.reasons[0] @@ -140,9 +140,7 @@ describe("AppProcess", () => { Effect.gen(function* () { const svc = yield* AppProcess.Service const exit = yield* Effect.exit( - svc - .runStream(cmd("-e", "console.log('a'); process.exit(2)"), { okExitCodes: [0] }) - .pipe(Stream.runCollect), + svc.runStream(cmd("-e", "console.log('a'); process.exit(2)"), { okExitCodes: [0] }).pipe(Stream.runCollect), ) expect(Exit.isFailure(exit)).toBe(true) if (Exit.isFailure(exit)) { @@ -169,9 +167,7 @@ describe("AppProcess", () => { "without okExitCodes, never fails on exit code", Effect.gen(function* () { const svc = yield* AppProcess.Service - const result = yield* svc - .runStream(cmd("-e", "console.log('only'); process.exit(7)")) - .pipe(Stream.runCollect) + const result = yield* svc.runStream(cmd("-e", "console.log('only'); process.exit(7)")).pipe(Stream.runCollect) expect(Array.from(result)).toEqual(["only"]) }), ) From d6367853aef34b5c5627e18014f9f8d0bdedaaac Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 13 May 2026 03:26:25 +0200 Subject: [PATCH 172/378] Add TUI notifications and attention sounds (disabled by default) (#26980) --- bun.lock | 390 +------------- bunfig.toml | 1 + package.json | 9 +- packages/opencode/package.json | 1 + packages/opencode/specs/tui-plugins.md | 34 ++ packages/opencode/specs/v2/notifications.md | 13 + packages/opencode/src/audio.d.ts | 5 + packages/opencode/src/cli/cmd/tui/app.tsx | 12 +- .../opencode/src/cli/cmd/tui/attention.ts | 261 ++++++++++ .../src/cli/cmd/tui/config/tui-schema.ts | 45 ++ .../opencode/src/cli/cmd/tui/config/tui.ts | 33 +- .../tui/feature-plugins/home/tips-view.tsx | 230 +++++++-- .../cli/cmd/tui/feature-plugins/home/tips.tsx | 4 +- .../feature-plugins/system/notifications.ts | 94 ++++ .../opencode/src/cli/cmd/tui/plugin/api.tsx | 2 + .../src/cli/cmd/tui/plugin/internal.ts | 2 + .../src/cli/cmd/tui/plugin/runtime.ts | 55 +- .../opencode/src/cli/cmd/tui/util/audio.ts | 58 +++ packages/opencode/src/util/filesystem.ts | 9 +- .../test/cli/cmd/tui/attention.test.ts | 484 ++++++++++++++++++ .../test/cli/cmd/tui/notifications.test.ts | 267 ++++++++++ .../test/cli/run/runtime.boot.test.ts | 75 ++- .../test/cli/tui/plugin-loader.test.ts | 80 +++ packages/opencode/test/config/tui.test.ts | 54 ++ packages/opencode/test/fixture/tui-plugin.ts | 19 +- packages/opencode/test/fixture/tui-runtime.ts | 12 +- packages/plugin/package.json | 6 +- packages/plugin/src/tui.ts | 81 +++ packages/ui/src/assets/audio/alert-01.mp3 | Bin 0 -> 11436 bytes packages/ui/src/assets/audio/alert-02.mp3 | Bin 0 -> 8667 bytes packages/ui/src/assets/audio/alert-03.mp3 | Bin 0 -> 3197 bytes packages/ui/src/assets/audio/alert-04.mp3 | Bin 0 -> 7369 bytes packages/ui/src/assets/audio/alert-05.mp3 | Bin 0 -> 7603 bytes packages/ui/src/assets/audio/alert-06.mp3 | Bin 0 -> 7516 bytes packages/ui/src/assets/audio/alert-07.mp3 | Bin 0 -> 6741 bytes packages/ui/src/assets/audio/alert-08.mp3 | Bin 0 -> 6171 bytes packages/ui/src/assets/audio/alert-09.mp3 | Bin 0 -> 5752 bytes packages/ui/src/assets/audio/alert-10.mp3 | Bin 0 -> 10780 bytes packages/ui/src/assets/audio/bip-bop-01.mp3 | Bin 0 -> 4574 bytes packages/ui/src/assets/audio/bip-bop-02.mp3 | Bin 0 -> 5697 bytes packages/ui/src/assets/audio/bip-bop-03.mp3 | Bin 0 -> 4186 bytes packages/ui/src/assets/audio/bip-bop-04.mp3 | Bin 0 -> 5436 bytes packages/ui/src/assets/audio/bip-bop-05.mp3 | Bin 0 -> 3534 bytes packages/ui/src/assets/audio/bip-bop-06.mp3 | Bin 0 -> 5796 bytes packages/ui/src/assets/audio/bip-bop-07.mp3 | Bin 0 -> 3925 bytes packages/ui/src/assets/audio/bip-bop-08.mp3 | Bin 0 -> 4390 bytes packages/ui/src/assets/audio/bip-bop-09.mp3 | Bin 0 -> 8654 bytes packages/ui/src/assets/audio/bip-bop-10.mp3 | Bin 0 -> 6760 bytes packages/ui/src/assets/audio/nope-01.mp3 | Bin 0 -> 3195 bytes packages/ui/src/assets/audio/nope-02.mp3 | Bin 0 -> 5753 bytes packages/ui/src/assets/audio/nope-03.mp3 | Bin 0 -> 3820 bytes packages/ui/src/assets/audio/nope-04.mp3 | Bin 0 -> 3562 bytes packages/ui/src/assets/audio/nope-05.mp3 | Bin 0 -> 5858 bytes packages/ui/src/assets/audio/nope-06.mp3 | Bin 0 -> 6066 bytes packages/ui/src/assets/audio/nope-07.mp3 | Bin 0 -> 3459 bytes packages/ui/src/assets/audio/nope-08.mp3 | Bin 0 -> 7450 bytes packages/ui/src/assets/audio/nope-09.mp3 | Bin 0 -> 5960 bytes packages/ui/src/assets/audio/nope-10.mp3 | Bin 0 -> 3981 bytes packages/ui/src/assets/audio/nope-11.mp3 | Bin 0 -> 13681 bytes packages/ui/src/assets/audio/nope-12.mp3 | Bin 0 -> 11595 bytes .../ui/src/assets/audio/staplebops-01.mp3 | Bin 0 -> 3665 bytes .../ui/src/assets/audio/staplebops-02.mp3 | Bin 0 -> 3898 bytes .../ui/src/assets/audio/staplebops-03.mp3 | Bin 0 -> 5905 bytes .../ui/src/assets/audio/staplebops-04.mp3 | Bin 0 -> 4553 bytes .../ui/src/assets/audio/staplebops-05.mp3 | Bin 0 -> 3458 bytes .../ui/src/assets/audio/staplebops-06.mp3 | Bin 0 -> 4971 bytes .../ui/src/assets/audio/staplebops-07.mp3 | Bin 0 -> 10376 bytes packages/ui/src/assets/audio/yup-01.mp3 | Bin 0 -> 4418 bytes packages/ui/src/assets/audio/yup-02.mp3 | Bin 0 -> 6061 bytes packages/ui/src/assets/audio/yup-03.mp3 | Bin 0 -> 7575 bytes packages/ui/src/assets/audio/yup-04.mp3 | Bin 0 -> 8674 bytes packages/ui/src/assets/audio/yup-05.mp3 | Bin 0 -> 10339 bytes packages/ui/src/assets/audio/yup-06.mp3 | Bin 0 -> 5905 bytes 73 files changed, 1856 insertions(+), 480 deletions(-) create mode 100644 packages/opencode/specs/v2/notifications.md create mode 100644 packages/opencode/src/cli/cmd/tui/attention.ts create mode 100644 packages/opencode/src/cli/cmd/tui/feature-plugins/system/notifications.ts create mode 100644 packages/opencode/src/cli/cmd/tui/util/audio.ts create mode 100644 packages/opencode/test/cli/cmd/tui/attention.test.ts create mode 100644 packages/opencode/test/cli/cmd/tui/notifications.test.ts create mode 100644 packages/ui/src/assets/audio/alert-01.mp3 create mode 100644 packages/ui/src/assets/audio/alert-02.mp3 create mode 100644 packages/ui/src/assets/audio/alert-03.mp3 create mode 100644 packages/ui/src/assets/audio/alert-04.mp3 create mode 100644 packages/ui/src/assets/audio/alert-05.mp3 create mode 100644 packages/ui/src/assets/audio/alert-06.mp3 create mode 100644 packages/ui/src/assets/audio/alert-07.mp3 create mode 100644 packages/ui/src/assets/audio/alert-08.mp3 create mode 100644 packages/ui/src/assets/audio/alert-09.mp3 create mode 100644 packages/ui/src/assets/audio/alert-10.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-01.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-02.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-03.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-04.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-05.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-06.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-07.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-08.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-09.mp3 create mode 100644 packages/ui/src/assets/audio/bip-bop-10.mp3 create mode 100644 packages/ui/src/assets/audio/nope-01.mp3 create mode 100644 packages/ui/src/assets/audio/nope-02.mp3 create mode 100644 packages/ui/src/assets/audio/nope-03.mp3 create mode 100644 packages/ui/src/assets/audio/nope-04.mp3 create mode 100644 packages/ui/src/assets/audio/nope-05.mp3 create mode 100644 packages/ui/src/assets/audio/nope-06.mp3 create mode 100644 packages/ui/src/assets/audio/nope-07.mp3 create mode 100644 packages/ui/src/assets/audio/nope-08.mp3 create mode 100644 packages/ui/src/assets/audio/nope-09.mp3 create mode 100644 packages/ui/src/assets/audio/nope-10.mp3 create mode 100644 packages/ui/src/assets/audio/nope-11.mp3 create mode 100644 packages/ui/src/assets/audio/nope-12.mp3 create mode 100644 packages/ui/src/assets/audio/staplebops-01.mp3 create mode 100644 packages/ui/src/assets/audio/staplebops-02.mp3 create mode 100644 packages/ui/src/assets/audio/staplebops-03.mp3 create mode 100644 packages/ui/src/assets/audio/staplebops-04.mp3 create mode 100644 packages/ui/src/assets/audio/staplebops-05.mp3 create mode 100644 packages/ui/src/assets/audio/staplebops-06.mp3 create mode 100644 packages/ui/src/assets/audio/staplebops-07.mp3 create mode 100644 packages/ui/src/assets/audio/yup-01.mp3 create mode 100644 packages/ui/src/assets/audio/yup-02.mp3 create mode 100644 packages/ui/src/assets/audio/yup-03.mp3 create mode 100644 packages/ui/src/assets/audio/yup-04.mp3 create mode 100644 packages/ui/src/assets/audio/yup-05.mp3 create mode 100644 packages/ui/src/assets/audio/yup-06.mp3 diff --git a/bun.lock b/bun.lock index 8e7a63a2e..76602e885 100644 --- a/bun.lock +++ b/bun.lock @@ -397,6 +397,7 @@ "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/ui": "workspace:*", "@openrouter/ai-sdk-provider": "2.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", @@ -507,9 +508,9 @@ "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.2.6", - "@opentui/keymap": ">=0.2.6", - "@opentui/solid": ">=0.2.6", + "@opentui/core": ">=0.2.8", + "@opentui/keymap": ">=0.2.8", + "@opentui/solid": ">=0.2.8", }, "optionalPeers": [ "@opentui/core", @@ -676,6 +677,9 @@ "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", }, "overrides": { + "@opentui/core": "catalog:", + "@opentui/keymap": "catalog:", + "@opentui/solid": "catalog:", "@types/bun": "catalog:", "@types/node": "catalog:", }, @@ -689,9 +693,9 @@ "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@opentui/core": "0.2.6", - "@opentui/keymap": "0.2.6", - "@opentui/solid": "0.2.6", + "@opentui/core": "0.2.8", + "@opentui/keymap": "0.2.8", + "@opentui/solid": "0.2.8", "@pierre/diffs": "1.1.0-beta.18", "@playwright/test": "1.59.1", "@sentry/solid": "10.36.0", @@ -1070,8 +1074,6 @@ "@develar/schema-utils": ["@develar/schema-utils@2.6.5", "", { "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } }, "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig=="], - "@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="], - "@dot/log": ["@dot/log@0.1.5", "", { "dependencies": { "chalk": "^4.1.2", "loglevelnext": "^6.0.0", "p-defer": "^3.0.0" } }, "sha512-ECraEVJWv2f2mWK93lYiefUkphStVlKD6yKDzisuoEmxuLKrxO9iGetHK2DoEAkj7sxjE886n0OUVVCUx0YPNg=="], "@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="], @@ -1288,62 +1290,6 @@ "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], - "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="], - - "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="], - - "@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="], - - "@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="], - - "@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="], - - "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="], - - "@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="], - - "@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="], - - "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="], - - "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="], - - "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="], - - "@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="], - - "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="], - - "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="], - - "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="], - - "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="], - - "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="], - - "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="], - - "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="], - - "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="], - - "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="], - - "@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="], - - "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="], - - "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="], - - "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="], - - "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="], - - "@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="], - - "@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="], - "@joshwooding/vite-plugin-react-docgen-typescript": ["@joshwooding/vite-plugin-react-docgen-typescript@0.7.0", "", { "dependencies": { "glob": "^13.0.1", "react-docgen-typescript": "^2.2.2" }, "peerDependencies": { "typescript": ">= 4.3.x", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["typescript"] }, "sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -1616,23 +1562,23 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.2.6", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.6", "@opentui/core-darwin-x64": "0.2.6", "@opentui/core-linux-arm64": "0.2.6", "@opentui/core-linux-x64": "0.2.6", "@opentui/core-win32-arm64": "0.2.6", "@opentui/core-win32-x64": "0.2.6" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-dBpMaWVM7wtW2/2TlGPrkPjg6gOL3MVU/5XXk+U1LDJB8L4q4NeYWVdzfAVNcEvgmuuCy/cVqdY2D4ei+e7MMg=="], + "@opentui/core": ["@opentui/core@0.2.8", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.8", "@opentui/core-darwin-x64": "0.2.8", "@opentui/core-linux-arm64": "0.2.8", "@opentui/core-linux-x64": "0.2.8", "@opentui/core-win32-arm64": "0.2.8", "@opentui/core-win32-x64": "0.2.8" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-bRRiCXuwjS8/6mN1oA5iVaf55z9APyalm7FnoxkLkEyIU1VDaQeTpYtElBbfo1rxtcO6Rj53XywH9oW8auNO9A=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hR5nsxNj+059utzenTCF0kealUlibON6fLuebFUCGM/5kJnqa+shIh0XbUDFm0+F47vqVUgZufBdUuieQZIbvQ=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Qh6VCMQgW3hWh/7MR51y+XuQezh8NOLwKS8EQSoKzAr4VOc/W5P0/DvgMKgwaqXw2Mz0AIba/BvZ6by20yc4zA=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-pJ/bH4WC/mbBaakM1YdH6TVo67jhy0KPd61bCz97w0I/PJGr8fmNKvhmMt/AwyFgOQi3FYZiEKLMpGdvUcSsrQ=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-wQjJ38C3IiVx/gwwBYxnCarzgD75FdS7IyUErt3lhn57XriNiCbb7ScphWnRMwwtL8CI+bBGzClroDRA2lCfvg=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Pnd3kOxig8ii+/IqYheOPEgferylsQA0L6tKBnHQ9jRlCJOcu0Rv65Jepueh212vevdV9DzPURJnhejG06J6g=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-fx4ADeWSSSVU1O/MkMnklCRxtWRy6CLeAvktLlNdPb+BhmQIDg1kpZcdv7m/3cgD1/ksFEXIwO6VTvfKYE0umw=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-458Mx9tBzEPzfft8cSt5ZaIpEepoxBXBOL6AUVmDTKWaZ3uouraPcEKraGAyvOTDQp2XDI3R8c/2GdaR77FaUQ=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.8", "", { "os": "linux", "cpu": "x64" }, "sha512-4ekUyzopBj2ClsUbneLnUOrmZtvU67FCVFLgmBfKL4IvVl/P0YobGNg71gN1JNiYpY7hK77qOpidVLHcNMIE7w=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-BDUrdrT1RCcVnQoHJmUut4y811jDBAEtc6GJFB4Gs265Be8SrTjVCus6p2fSQ7j9sZQ1OcjO+5+4NkheSZICDQ=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-63K046wpzTzQOLOG9LTsp3+Ld0TNTxeQczexkg0pKSBxZFhws+/9YIGjTctZmJUfE1g1X4tI31dO+KNRpXRHQw=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-SUYAzRJ9TSoD2Qt8kn6FJz6dbTrFEPVig5mScB4zFGgGQO/Bbod2/Q31vLS/IQrX+FDb67WaErD+kuMCnMPPLA=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.8", "", { "os": "win32", "cpu": "x64" }, "sha512-+WDiTlTyDpgkis8rPAhW1fS7TwXJih+fk+RYXS2bC3tAKsRD+O3PRSkVABRbjkuXbtfJZf2cjOHZFGN4Vf5qDg=="], - "@opentui/keymap": ["@opentui/keymap@0.2.6", "", { "dependencies": { "@opentui/core": "0.2.6" }, "peerDependencies": { "@opentui/react": "0.2.6", "@opentui/solid": "0.2.6", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-+6OYuedrFCKVo4ryGFNwws++2VOmPcXU3PwpY0mP47gYQY2nvQ+etWIs2Y7r5eMIqUfxVCldkKsrzcEcA4tb/A=="], + "@opentui/keymap": ["@opentui/keymap@0.2.8", "", { "dependencies": { "@opentui/core": "0.2.8" }, "peerDependencies": { "@opentui/react": "0.2.8", "@opentui/solid": "0.2.8", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-/H9j8fP64cf3/nFDCvVP8+7cwU/oRh4sgfQH2NhcPp8illgBb/e9pG5x3vM0nK4RVyTqUvkPXsOeIX5u7vltlg=="], - "@opentui/solid": ["@opentui/solid@0.2.6", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.6", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-2y225WlOGi/fCaajkxBmLyVW8Cr+OmhowHdvrYcz5w2kBD15sKbJLIYu1G9DxceirT1uIyasGy2TGzRRcVkTDg=="], + "@opentui/solid": ["@opentui/solid@0.2.8", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.8", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-f2g0riBuzk4/ZmcJnp1k13odUmNZcfA3nF7RzdSlEfpkwNDfc4xqnRAwYbNNDwGNrJX0JDCTEZY5ZEhuL155MQ=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -2284,8 +2230,6 @@ "@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="], - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="], "@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="], @@ -2550,8 +2494,6 @@ "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], - "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="], - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], @@ -2620,8 +2562,6 @@ "avvio": ["avvio@9.2.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ=="], - "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="], - "aws-sdk": ["aws-sdk@2.1692.0", "", { "dependencies": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", "util": "^0.12.4", "uuid": "8.0.0", "xml2js": "0.6.2" } }, "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw=="], "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], @@ -2686,8 +2626,6 @@ "blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="], - "bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="], - "body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="], "bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="], @@ -2730,16 +2668,6 @@ "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=="], - - "bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lIsDkPzJzPl6yrB5CUOINJFPnTRv6fF/Q8J1mAr43ogSp86WZEg9XZKaT6f3EUJ+9ETogGoMnoj1q0AwHUTbAQ=="], - - "bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-uEddf5U7GvKIkM/BV18rUKtYHL6d0KeqBjNHwfqDH9QgEo9KVSKvJXS5I/sMefk5V5pIYE+8tQhtrREevhocng=="], - - "bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-Y/f15j9r8ba0xUz+3lATtS74OE+PPzQXO7Do/1eCluJcuOlfa77kMjvBK/ShWnem3Y9xqi59pebTPOGRB+CaJA=="], - - "bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-MHSFAKqizISb+C5NfDrFe3g0Al5Njnu0j/A+oO2Q+bIWX+fUYjBSowiYE1ZXJx65KuryuB+tiM7Qh6cQbVvkEg=="], - "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -3160,8 +3088,6 @@ "execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], - "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="], - "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], @@ -3224,8 +3150,6 @@ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], - "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], - "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -3316,8 +3240,6 @@ "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#20bd361", {}, "anomalyco-ghostty-web-20bd361", "sha512-dW0nwaiBBcun9y5WJSvm3HxDLe5o9V0xLCndQvWonRVubU8CS1PHxZpLffyPt1YujPWC13ez03aWxcuKBPYYGQ=="], - "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], - "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], @@ -3470,8 +3392,6 @@ "ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="], - "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="], - "immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="], "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], @@ -3614,16 +3534,12 @@ "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], - "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="], - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jmespath": ["jmespath@0.16.0", "", {}, "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="], "jose": ["jose@6.0.11", "", {}, "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg=="], - "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], - "js-base64": ["js-base64@3.7.7", "", {}, "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="], "js-beautify": ["js-beautify@1.15.4", "", { "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", "glob": "^10.4.2", "js-cookie": "^3.0.5", "nopt": "^7.2.1" }, "bin": { "css-beautify": "js/bin/css-beautify.js", "html-beautify": "js/bin/html-beautify.js", "js-beautify": "js/bin/js-beautify.js" } }, "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="], @@ -4084,8 +4000,6 @@ "oidc-token-hash": ["oidc-token-hash@5.2.0", "", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="], - "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], - "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -4160,12 +4074,6 @@ "param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="], - "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="], - - "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="], - - "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="], - "parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -4210,8 +4118,6 @@ "peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="], - "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="], - "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], @@ -4232,8 +4138,6 @@ "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="], - "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], @@ -4242,16 +4146,12 @@ "pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="], - "planck": ["planck@1.5.0", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-dlvqJE+FscZgrGUXJ5ybd0o5bvZ5XXyZNbm08xGsXp9WjXeAyWSFT6n9s/1PQcUBo4546fDXA5RMA4wbDyZw6g=="], - "playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="], "playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="], "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], - "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], - "poe-oauth": ["poe-oauth@0.0.6", "", {}, "sha512-dI8xrVl7RSFh0B+cb4GGuCjIfGtDT9VpbpVkP0UKcunpXF0eFw+6GencoJ7k+E02ZYqopBQApMVWGq70/GP69w=="], "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], @@ -4376,8 +4276,6 @@ "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], - "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], @@ -4584,8 +4482,6 @@ "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], - "simple-xml-to-json": ["simple-xml-to-json@1.2.7", "", {}, "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q=="], - "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], "sitemap": ["sitemap@9.0.1", "", { "dependencies": { "@types/node": "^24.9.2", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/esm/cli.js" } }, "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ=="], @@ -4672,8 +4568,6 @@ "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="], - "stage-js": ["stage-js@1.0.2", "", {}, "sha512-EWTRBYlg7Qv9wGUao99/PfRe3KaiQqWmgSvTOXvaWnu1Jk/q/vV8yJVu6bi/3EqDZeMVnCPAjheba6OFc5k1GQ=="], - "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], @@ -4722,8 +4616,6 @@ "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], - "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="], - "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="], "stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="], @@ -4776,8 +4668,6 @@ "thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="], - "three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="], - "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], @@ -4790,8 +4680,6 @@ "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], - "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], @@ -4812,8 +4700,6 @@ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="], - "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="], "toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.8", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-b+5ynEFp4Woe5a22hzNQm42lD23t13ZMihVxHbzjA50zdcM9aOSJTIjdJ0PDSd4/50HbBXcpHiQsz6rM4N88ww=="], @@ -4966,8 +4852,6 @@ "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], - "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="], - "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], @@ -5096,8 +4980,6 @@ "xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="], - "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="], - "xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], @@ -5450,46 +5332,10 @@ "@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=="], - "@happy-dom/global-registrator/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "@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=="], - "@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - - "@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-circle/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-color/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-contain/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-cover/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-crop/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-displace/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-fisheye/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-flip/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-mask/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-print/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-quantize/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-resize/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-rotate/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/plugin-threshold/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@jimp/types/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@jsx-email/cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "@jsx-email/cli/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], @@ -5628,24 +5474,16 @@ "@slack/bolt/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], - "@slack/logger/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "@slack/oauth/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="], - "@slack/oauth/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "@slack/socket-mode/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="], - "@slack/socket-mode/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "@slack/socket-mode/@types/ws": ["@types/ws@7.4.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww=="], "@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=="], - "@slack/web-api/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "@slack/web-api/eventemitter3": ["eventemitter3@3.1.2", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="], "@slack/web-api/form-data": ["form-data@2.5.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", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="], @@ -5706,62 +5544,8 @@ "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], - "@types/body-parser/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/cacache/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/cacheable-request/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/connect/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/cross-spawn/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/express-serve-static-core/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/fontkit/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/fs-extra/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/is-stream/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/jsonwebtoken/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/keyv/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/mssql/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/node-fetch/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/npm-registry-fetch/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/npmcli__arborist/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/npmlog/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/pacote/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/plist/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - "@types/readable-stream/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/responselike/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/sax/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/send/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/serve-static/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/ssri/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/tunnel/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/ws/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "@types/yauzl/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], "@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], @@ -5838,18 +5622,12 @@ "builder-util-runtime/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], - "bun-types/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - - "bun-webgpu/@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="], - "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], "c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], - "cloudflare/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], "condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], @@ -5884,8 +5662,6 @@ "effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "electron/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "electron-builder/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "electron-builder/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -5940,8 +5716,6 @@ "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], - "happy-dom/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "happy-dom/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=="], "html-minifier-terser/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], @@ -5954,8 +5728,6 @@ "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], - "image-q/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "js-beautify/nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], @@ -6028,9 +5800,9 @@ "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "opentui-spinner/@opentui/core": ["@opentui/core@0.1.105", "", { "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.105", "@opentui/core-darwin-x64": "0.1.105", "@opentui/core-linux-arm64": "0.1.105", "@opentui/core-linux-x64": "0.1.105", "@opentui/core-win32-arm64": "0.1.105", "@opentui/core-win32-x64": "0.1.105", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-vllSOOCW6VIThV/96GRLJ1IxIBuR+ci6FDvnPIAG4s7SJ/FW6zAkqDn1xrtBwwk/lM3QWjLqy8BZc+zwWvveJA=="], + "opentui-spinner/@opentui/core": ["@opentui/core@0.2.7", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.7", "@opentui/core-darwin-x64": "0.2.7", "@opentui/core-linux-arm64": "0.2.7", "@opentui/core-linux-x64": "0.2.7", "@opentui/core-win32-arm64": "0.2.7", "@opentui/core-win32-x64": "0.2.7" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-cnN6JcaGC7SeQzobBy/CHzqUAQFtypazuw1CjQBo7WwoOiLMGubt9W5FXeF0zIrSxH2Ed6NLWhPYRg7SD4629Q=="], - "opentui-spinner/@opentui/solid": ["@opentui/solid@0.1.105", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.105", "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-uxnaMP802sCI487pv/Hk9xdFdIj9mkg3eNliAqbqR0Shmd4phcjKEZvPRpijjmI99j4s9nul71jzF3h1oz31Nw=="], + "opentui-spinner/@opentui/solid": ["@opentui/solid@0.2.7", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.7", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-nlkx9HvuWaHtc5A8eUEAPNi+5+37LZS3ln73WRmtT5xin8LnQf+yhwopqGgPSnLq1ODLwhkKRdr/9JCDr2j7Bg=="], "ora/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], @@ -6044,14 +5816,10 @@ "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=="], - "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], - "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "pkg-up/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="], @@ -6076,8 +5844,6 @@ "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "protobufjs/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], @@ -6108,8 +5874,6 @@ "shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], - "sitemap/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "sitemap/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -6132,20 +5896,14 @@ "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "stripe/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "tedious/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - "token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "tree-sitter-bash/node-addon-api": ["node-addon-api@8.7.0", "", {}, "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA=="], "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=="], @@ -6162,8 +5920,6 @@ "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - "venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], "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=="], @@ -6478,8 +6234,6 @@ "@gitlab/opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], - "@happy-dom/global-registrator/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "@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=="], @@ -6674,14 +6428,6 @@ "@sentry/cli/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "@slack/logger/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@slack/oauth/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@slack/socket-mode/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@slack/web-api/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -6708,60 +6454,6 @@ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - "@types/body-parser/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/cacache/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/cacheable-request/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/connect/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/cross-spawn/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/express-serve-static-core/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/fontkit/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/fs-extra/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/is-stream/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/jsonwebtoken/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/keyv/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/mssql/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/node-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/npm-registry-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/npmcli__arborist/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/npmlog/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/pacote/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/plist/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/readable-stream/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/responselike/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/sax/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/send/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/serve-static/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/ssri/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/tunnel/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "@types/yauzl/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -6810,12 +6502,8 @@ "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "cloudflare/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "crc/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -6834,8 +6522,6 @@ "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "electron/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -6848,14 +6534,10 @@ "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "happy-dom/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "iconv-corefoundation/cli-truncate/slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], "iconv-corefoundation/cli-truncate/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=="], - "image-q/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "js-beautify/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "js-beautify/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -6874,8 +6556,6 @@ "motion/framer-motion/motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], - "mssql/tedious/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="], - "mssql/tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], @@ -6894,24 +6574,22 @@ "opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "opentui-spinner/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.105", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1pIL7aer9amwj8EpYoMNtvavKetIe+nX8uBRmYsMQb+KvJoUAZUqENfRW+qHE5WrsOyxx8/QoyXTHw15GG5iLQ=="], + "opentui-spinner/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CAy6cL3byz2Xf6gFiJHBpcnsp/2ADEWLLOUokVypOyPLcy8GY3sPzlA4pkAjVGQMYQhDj+Y3+SXz4uTLt4AETg=="], - "opentui-spinner/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.105", "", { "os": "darwin", "cpu": "x64" }, "sha512-hLIRSWlK3gY2NRXJGWiTBiMYSmRDjOYFZF6WtUVXhY2SL3sp08dhmr/6dmAVH+3pKCsCipLEsrrcQX6SAihCTA=="], + "opentui-spinner/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-K06h333rMkC9cyMJr/VvcRK3ik81Admd8ZsES5uf5YXWPdYhXGf75I1T8mKIThhUmoFLb8R5xqfuPmoocsjM7Q=="], - "opentui-spinner/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.105", "", { "os": "linux", "cpu": "arm64" }, "sha512-jlRKfPkozTZEkHEePuCWYcTIUtPm+ieInAwGVqGmjbvqjxdVv1/W/Dt6LEZ/9jpRiOPd+FjXAfLe6wa/XWHr+w=="], + "opentui-spinner/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-iYWGTztbdG9yYSB5Alxuo0dWAmkWQR0+/paNWUyPOocjigmKgMmACDtHgYqa7sxkIcWgmXljt/f8rgXDG4wdMg=="], - "opentui-spinner/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.105", "", { "os": "linux", "cpu": "x64" }, "sha512-kfWS1WMg6qHShmxZX9s1tZc/8JcXw6uyy2UtyTbJdRFExtXGH37oKHi8QK8iPL2ExCx4z7zqVnVJfO3X/Wh7lA=="], + "opentui-spinner/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.7", "", { "os": "linux", "cpu": "x64" }, "sha512-tymBCfYbsDRfHQNXsolkFfaTEIDhemD4+1ZovUztQd7i+0Ggnu9WbPN1SNCiRz6PjrlaNeQzZE3Wl8FfVdw/cw=="], - "opentui-spinner/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.105", "", { "os": "win32", "cpu": "arm64" }, "sha512-UFx6A8OpBVbGWK6OAw4GqAqKZgIITJfSOd35pG9yDVKQouHN2OGc2HeeXrH2A4h42p40Xl6IfcqqfllkpC13Dg=="], + "opentui-spinner/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-XLPJWdT8QOukrYDkpIng6+uNUlF66ByXcQlC3qA9JbrUTBetZhgXs8Q2jEjRfc+Ty3uh1iRSA6PgJGbbOK/f4Q=="], - "opentui-spinner/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.105", "", { "os": "win32", "cpu": "x64" }, "sha512-f9FqqUmxehwhF+cgyazm0YT0v0BYTTCPzd6eztqhl74N3x/kC+jOOz2rdJDC/tTBo1JVsF64KupOnhIs6/Cogg=="], + "opentui-spinner/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.7", "", { "os": "win32", "cpu": "x64" }, "sha512-CzVGEfqysVk8Hxcj0RDv/DtXIM6iZmbmr23kW7y8CJMPtmV1gmKI4D9abVjynWJnGbaSBnDi43mgZnGMgOdyEg=="], - "opentui-spinner/@opentui/core/bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], + "opentui-spinner/@opentui/core/diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], "opentui-spinner/@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], - "opentui-spinner/@opentui/solid/babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="], - "ora/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "ora/bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], @@ -6920,14 +6598,10 @@ "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "parse-bmfont-xml/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], - "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], - "protobufjs/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], @@ -6938,16 +6612,10 @@ "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "sitemap/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "stripe/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "tedious/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "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=="], @@ -7250,8 +6918,6 @@ "js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "mssql/tedious/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], diff --git a/bunfig.toml b/bunfig.toml index 363579bbf..47c4ac539 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -2,6 +2,7 @@ exact = true # Only install newly resolved package versions published at least 3 days ago. minimumReleaseAge = 259200 +minimumReleaseAgeExcludes = ["@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-x64", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid"] [test] root = "./do-not-run-tests-from-root" diff --git a/package.json b/package.json index 6d82864d6..f1cc7da5c 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ "@types/cross-spawn": "6.0.6", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", - "@opentui/core": "0.2.6", - "@opentui/keymap": "0.2.6", - "@opentui/solid": "0.2.6", + "@opentui/core": "0.2.8", + "@opentui/keymap": "0.2.8", + "@opentui/solid": "0.2.8", "ulid": "3.0.1", "@kobalte/core": "0.13.11", "@types/luxon": "3.7.1", @@ -128,6 +128,9 @@ "electron" ], "overrides": { + "@opentui/core": "catalog:", + "@opentui/keymap": "catalog:", + "@opentui/solid": "catalog:", "@types/bun": "catalog:", "@types/node": "catalog:" }, diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 121b34c3a..b0427f231 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -108,6 +108,7 @@ "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/ui": "workspace:*", "@openrouter/ai-sdk-provider": "2.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", diff --git a/packages/opencode/specs/tui-plugins.md b/packages/opencode/specs/tui-plugins.md index c1a9b271c..e9ece6c0a 100644 --- a/packages/opencode/specs/tui-plugins.md +++ b/packages/opencode/specs/tui-plugins.md @@ -29,6 +29,16 @@ Example: "plugin": ["@acme/opencode-plugin@1.2.3", ["./plugins/demo.tsx", { "label": "demo" }]], "plugin_enabled": { "acme.demo": false + }, + "attention": { + "enabled": true, + "notifications": true, + "sound": true, + "volume": 0.4, + "sound_pack": "opencode.default", + "sounds": { + "error": "/Users/me/sounds/error.mp3" + } } } ``` @@ -45,6 +55,11 @@ Example: - Internal plugins can declare `enabled: false` to be registered but inactive by default; `plugin_enabled` and runtime KV can still enable them by id. - `plugin_enabled` is merged across config layers. - Runtime enable/disable state is also stored in KV under `plugin_enabled`; that KV state overrides config on startup. +- `attention.enabled` defaults to `false`; when `false`, it disables all `api.attention.notify(...)` delivery. +- `attention.notifications` and `attention.sound` independently control terminal-mediated desktop notifications and built-in sounds. +- `attention.volume` sets the default built-in sound volume from `0` to `1`. +- `attention.sound_pack` selects the initial semantic sound pack. Persisted runtime selection in KV can override it. +- `attention.sounds` overrides individual semantic sound slots such as `error`, `done`, or `subagent_done`. - `leader_timeout` is a top-level TUI setting. - `keybinds` is a flat object keyed by command id; values are key binding values (`false`, `"none"`, a key string/object, a binding object, or an array of key strings/objects/binding objects). - `keybinds.leader` sets the key used by `` shortcuts. @@ -212,6 +227,7 @@ That is what makes local config-scoped plugins able to import `@opencode-ai/plug Top-level API groups exposed to `tui(api, options, meta)`: - `api.app.version` +- `api.attention.notify(input)` - `api.keys.formatSequence(parts)`, `formatBindings(bindings)` - `api.keymap` - `api.route.register(routes)` / `api.route.navigate(name, params?)` / `api.route.current` @@ -246,6 +262,24 @@ Top-level API groups exposed to `tui(api, options, meta)`: - `formatBindings(bindings)` formats binding lists and returns `undefined` when there is nothing to show. - For generic config-to-bindings helpers, import `createBindingLookup` from `@opencode-ai/plugin/tui`. +### Attention + +- `api.attention.notify({ title?, message, notification?, sound? })` requests user attention while keeping terminal focus, notifications, and audio owned by the host. +- `message` is required; `title` defaults to `"opencode"`; `notification` defaults to enabled with `when: "blurred"`; `sound` defaults to enabled with `when: "always"`. +- `when: "always"` requests delivery regardless of terminal focus state. +- `when: "focused"` only requests delivery after the terminal is known focused; `when: "blurred"` only requests delivery after the terminal is known blurred. +- Example: `notification: { when: "blurred" }, sound: { name: "question", when: "always" }` plays sound while focused but only triggers system notifications when blurred. +- Semantic sound names are `"default"`, `"question"`, `"permission"`, `"error"`, `"done"`, and `"subagent_done"`. +- `sound: true` plays the `"default"` sound; `sound: { name: "question" }` plays a named semantic sound. +- `sound: { volume }` overrides volume for that call; `sound: false` disables sound for that call; `notification: false` disables system notification for that call. +- `api.attention.soundboard.registerPack({ id, name?, sounds })` registers a sound pack and returns a disposer. Relative paths resolve from the plugin root and are cleaned up on plugin deactivation. +- `api.attention.soundboard.activate(id, { persist })` selects the active pack. `persist: true` writes the selected pack id to TUI KV state, not `tui.json`. +- `api.attention.soundboard.current()` and `list()` expose the active/registered packs for plugin UX. +- Config `attention.sounds` overrides active-pack sounds by slot. Failed loads fall back to the active pack and then `opencode.default`. +- The host strips ANSI/control characters and collapses newlines before sending text to the terminal notification API. +- Terminal and OS settings decide whether a requested notification is visibly displayed. +- Prefer privacy-safe messages such as `"A question needs your input"`; avoid full commands, paths, prompts, errors, secrets, or file contents unless the plugin intentionally exposes them. + ### Routes - Reserved route names: `home` and `session`. diff --git a/packages/opencode/specs/v2/notifications.md b/packages/opencode/specs/v2/notifications.md new file mode 100644 index 000000000..96018f994 --- /dev/null +++ b/packages/opencode/specs/v2/notifications.md @@ -0,0 +1,13 @@ +# TUI Notifications Default + +Problem: + +- v1 defaults `attention.enabled` to `false` +- users can opt in with `attention.enabled = true` +- v2 should make core TUI notifications a default behavior + +## v2 Target + +Flip `attention.enabled` to `true` by default in v2. + +Keep `attention.enabled = false` as the explicit opt-out. diff --git a/packages/opencode/src/audio.d.ts b/packages/opencode/src/audio.d.ts index c7c947450..7b99d097a 100644 --- a/packages/opencode/src/audio.d.ts +++ b/packages/opencode/src/audio.d.ts @@ -3,6 +3,11 @@ declare module "*.wav" { export default file } +declare module "*.mp3" { + const file: string + export default file +} + declare module "*.wasm" { const file: string export default file diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index d7f2cd14b..29cca133b 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -2,6 +2,7 @@ import { render, TimeToFirstDraw, useRenderer, useTerminalDimensions } from "@op import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui" import * as Clipboard from "@tui/util/clipboard" import * as Selection from "@tui/util/selection" +import * as TuiAudio from "@tui/util/audio" import { createCliRenderer, MouseButton, type CliRendererConfig } from "@opentui/core" import { RouteProvider, useRoute } from "@tui/context/route" import { @@ -63,6 +64,7 @@ import { TuiConfig } from "@/cli/cmd/tui/config/tui" import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime" import { createTuiApi } from "@/cli/cmd/tui/plugin/api" import type { RouteMap } from "@/cli/cmd/tui/plugin/api" +import { createTuiAttention } from "@/cli/cmd/tui/attention" import { FormatError, FormatUnknownError } from "@/cli/error" import { CommandPaletteProvider, useCommandPalette } from "./context/command-palette" import { OpencodeKeymapProvider, registerOpencodeKeymap, useBindings, useOpencodeKeymap } from "./keymap" @@ -176,10 +178,10 @@ export function tui(input: { unguard?.() resolve() } - const onBeforeExit = async () => { offKeymap() await TuiPluginRuntime.dispose() + TuiAudio.dispose() } const renderer = await createCliRenderer(rendererConfig(input.config)) @@ -283,6 +285,7 @@ function App(props: { onSnapshot?: () => Promise }) { routeRev() return routes.get(name)?.at(-1)?.render } + const attention = createTuiAttention({ renderer, config: tuiConfig, kv }) const api = createTuiApi({ tuiConfig, @@ -298,11 +301,13 @@ function App(props: { onSnapshot?: () => Promise }) { theme: themeState, toast, renderer, + attention, }) const [ready, setReady] = createSignal(false) TuiPluginRuntime.init({ api, config: tuiConfig, + dispose: () => attention.dispose(), }) .catch((error) => { console.error("Failed to load TUI plugins", error) @@ -320,7 +325,10 @@ function App(props: { onSnapshot?: () => Promise }) { }, { priority: 1 }, ) - onCleanup(offSelectionKeys) + onCleanup(() => { + offSelectionKeys() + attention.dispose() + }) // Wire up console copy-to-clipboard via opentui's onCopySelection callback renderer.console.onCopySelection = async (text: string) => { diff --git a/packages/opencode/src/cli/cmd/tui/attention.ts b/packages/opencode/src/cli/cmd/tui/attention.ts new file mode 100644 index 000000000..60bd97b97 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/attention.ts @@ -0,0 +1,261 @@ +import type { + TuiAttention, + TuiAttentionNotifyInput, + TuiAttentionNotifyResult, + TuiAttentionNotifySkipReason, + TuiAttentionWhen, + TuiKV, + TuiAttentionSoundName, + TuiAttentionSoundPack, + TuiAttentionSoundPackInfo, +} from "@opencode-ai/plugin/tui" +import stripAnsi from "strip-ansi" +import type { TuiConfig } from "./config/tui" +import { isAttentionSoundName } from "./config/tui-schema" +import * as TuiAudio from "@tui/util/audio" +import defaultSoundPath from "@opencode-ai/ui/audio/bip-bop-01.mp3" with { type: "file" } +import questionSoundPath from "@opencode-ai/ui/audio/bip-bop-03.mp3" with { type: "file" } +import permissionSoundPath from "@opencode-ai/ui/audio/staplebops-06.mp3" with { type: "file" } +import errorSoundPath from "@opencode-ai/ui/audio/nope-03.mp3" with { type: "file" } +import doneSoundPath from "@opencode-ai/ui/audio/bip-bop-01.mp3" with { type: "file" } +import subagentDoneSoundPath from "@opencode-ai/ui/audio/yup-01.mp3" with { type: "file" } +import * as Log from "@opencode-ai/core/util/log" + +type FocusState = "unknown" | "focused" | "blurred" + +type AttentionRenderer = { + readonly isDestroyed: boolean + on(event: "focus" | "blur", listener: () => void): unknown + off(event: "focus" | "blur", listener: () => void): unknown + triggerNotification(message: string, title?: string): boolean +} + +type RegisteredSoundPack = TuiAttentionSoundPack & { + builtin: boolean +} + +type TuiAttentionHost = TuiAttention & { + dispose(): void +} + +const log = Log.create({ service: "tui.attention" }) + +const DEFAULT_TITLE = "opencode" +const DEFAULT_PACK_ID = "opencode.default" +const KV_SOUND_PACK = "attention_sound_pack" +const TITLE_LIMIT = 80 +const MESSAGE_LIMIT = 240 +const BUILTIN_PACK: RegisteredSoundPack = { + id: DEFAULT_PACK_ID, + name: "OpenCode Default", + builtin: true, + sounds: { + default: defaultSoundPath, + question: questionSoundPath, + permission: permissionSoundPath, + error: errorSoundPath, + done: doneSoundPath, + subagent_done: subagentDoneSoundPath, + }, +} + +function skipped(reason: TuiAttentionNotifySkipReason): TuiAttentionNotifyResult { + return { + ok: false, + notification: false, + sound: false, + skipped: reason, + } +} + +function normalizeText(input: string | undefined, fallback: string, limit: number) { + const text = stripAnsi(input ?? "") + .replace(/[ \t]*[\r\n]+[ \t]*/g, " ") + .replace(/[\u0000-\u0009\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, "") + .trim() + const normalized = text.length ? text : fallback + return Array.from(normalized).slice(0, limit).join("") +} + +function clampVolume(volume: number) { + if (!Number.isFinite(volume)) return 0 + return Math.min(1, Math.max(0, volume)) +} + +function soundVolume(input: TuiAttentionNotifyInput, config: Pick) { + if (!config.attention.sound) return + if (input.sound === false) return + if (input.sound === undefined) return clampVolume(config.attention.volume) + if (input.sound === true) return clampVolume(config.attention.volume) + return clampVolume(input.sound.volume ?? config.attention.volume) +} + +function normalizePack(pack: TuiAttentionSoundPack): RegisteredSoundPack | undefined { + const id = pack.id.trim() + if (!id) return + return { + id, + name: pack.name?.trim() || undefined, + builtin: false, + sounds: Object.fromEntries( + Object.entries(pack.sounds).filter( + (item): item is [TuiAttentionSoundName, string] => + isAttentionSoundName(item[0]) && typeof item[1] === "string" && item[1].trim().length > 0, + ), + ), + } +} + +function focusSkip(when: TuiAttentionWhen, focus: FocusState) { + if (when === "always") return + if (focus === "unknown") return "focus_unknown" + if (when === "blurred" && focus === "focused") return "focused" + if (when === "focused" && focus === "blurred") return "blurred" +} + +export function createTuiAttention(input: { + renderer: AttentionRenderer + config: Pick + kv?: TuiKV + audio?: Pick +}): TuiAttentionHost { + let focus: FocusState = "unknown" + let disposed = false + let activePackID: string | undefined + const packs = new Map([[BUILTIN_PACK.id, BUILTIN_PACK]]) + const audio = input.audio ?? TuiAudio + + const onFocus = () => { + focus = "focused" + } + const onBlur = () => { + focus = "blurred" + } + + input.renderer.on("focus", onFocus) + input.renderer.on("blur", onBlur) + + function configuredPackID() { + const stored = input.kv?.get(KV_SOUND_PACK, undefined) + return activePackID ?? stored ?? input.config.attention.sound_pack + } + + function currentPack() { + return packs.get(configuredPackID()) ?? BUILTIN_PACK + } + + function soundCandidates(name: TuiAttentionSoundName) { + return [input.config.attention.sounds[name], currentPack().sounds[name], BUILTIN_PACK.sounds[name]].filter( + (item, index, list): item is string => typeof item === "string" && list.indexOf(item) === index, + ) + } + + async function playSound(name: TuiAttentionSoundName, volume: number) { + try { + for (const file of soundCandidates(name)) { + const current = await audio.loadSoundFile(file).catch((error) => { + log.debug("failed to load attention sound", { file, error }) + return null + }) + if (disposed) return false + if (current == null) continue + if (audio.play(current, { volume }) != null) return true + } + return false + } catch (error) { + log.debug("failed to play attention sound", { error }) + return false + } + } + + return { + async notify(request) { + try { + if (!input.config.attention.enabled) return skipped("attention_disabled") + if (disposed || input.renderer.isDestroyed) return skipped("renderer_destroyed") + + const message = normalizeText(request.message, "", MESSAGE_LIMIT) + if (!message) return skipped("empty_message") + + const requestedNotification = typeof request.notification === "object" ? request.notification : undefined + const notificationSkip = focusSkip(requestedNotification?.when ?? "blurred", focus) + const notificationRequested = input.config.attention.notifications && request.notification !== false + const shouldNotify = notificationRequested && !notificationSkip + const notification = shouldNotify + ? (() => { + try { + return input.renderer.triggerNotification( + message, + normalizeText(request.title, DEFAULT_TITLE, TITLE_LIMIT), + ) + } catch (error) { + log.debug("failed to trigger attention notification", { error }) + return false + } + })() + : false + const volume = soundVolume(request, input.config) + const requestedSound = typeof request.sound === "object" ? request.sound : undefined + const soundSkip = volume === undefined ? undefined : focusSkip(requestedSound?.when ?? "always", focus) + const soundName = requestedSound?.name && isAttentionSoundName(requestedSound.name) ? requestedSound.name : "default" + const sound = volume === undefined || soundSkip ? false : await playSound(soundName, volume) + + if (!notification && !sound) { + if (notificationRequested && notificationSkip) return skipped(notificationSkip) + if (soundSkip) return skipped(soundSkip) + } + + return { + ok: notification || sound, + notification, + sound, + } + } catch (error) { + log.debug("failed to handle attention notification", { error }) + return { + ok: false, + notification: false, + sound: false, + } + } + }, + soundboard: { + registerPack(pack) { + const next = normalizePack(pack) + if (!next) return () => {} + packs.set(next.id, next) + let disposed = false + return () => { + if (disposed) return + disposed = true + if (packs.get(next.id) === next) packs.delete(next.id) + } + }, + activate(id, options) { + const pack = packs.get(id) + if (!pack) return false + activePackID = pack.id + if (options?.persist) input.kv?.set(KV_SOUND_PACK, pack.id) + return true + }, + current() { + return currentPack().id + }, + list(): TuiAttentionSoundPackInfo[] { + const current = currentPack().id + return Array.from(packs.values()).map((pack) => ({ + id: pack.id, + name: pack.name, + active: pack.id === current, + builtin: pack.builtin, + })) + }, + }, + dispose() { + if (disposed) return + disposed = true + input.renderer.off("focus", onFocus) + input.renderer.off("blur", onBlur) + }, + } +} diff --git a/packages/opencode/src/cli/cmd/tui/config/tui-schema.ts b/packages/opencode/src/cli/cmd/tui/config/tui-schema.ts index 80765da3c..2c99f2a5e 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui-schema.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui-schema.ts @@ -1,12 +1,47 @@ import { ConfigPlugin } from "@/config/plugin" import { TuiKeybind } from "./keybind" import { Schema } from "effect" +import { isRecord } from "@/util/record" +import { Filesystem } from "@/util/filesystem" +import { TuiAttentionSoundNames, type TuiAttentionSoundName } from "@opencode-ai/plugin/tui" + +export type TuiAttentionSoundPaths = Partial> + +export function isAttentionSoundName(value: string): value is TuiAttentionSoundName { + return TuiAttentionSoundNames.includes(value as TuiAttentionSoundName) +} + +export function resolveAttentionSoundPaths( + root: string, + sounds: unknown, + options?: { trim?: boolean }, +): TuiAttentionSoundPaths { + if (!isRecord(sounds)) return {} + return Object.fromEntries( + Object.entries(sounds).flatMap(([name, file]) => { + if (!isAttentionSoundName(name)) return [] + if (typeof file !== "string") return [] + const value = options?.trim ? file.trim() : file + if (!value) return [] + return [[name, Filesystem.resolveFilePath(root, value)]] + }), + ) +} export const KeymapLeaderTimeoutDefault = 2000 const KeymapLeaderTimeout = Schema.Int.check(Schema.isGreaterThan(0)).annotate({ description: "Leader key timeout in milliseconds", }) +const TuiAttentionSounds = Schema.Struct({ + default: Schema.optional(Schema.String), + question: Schema.optional(Schema.String), + permission: Schema.optional(Schema.String), + error: Schema.optional(Schema.String), + done: Schema.optional(Schema.String), + subagent_done: Schema.optional(Schema.String), +}) + export const ScrollSpeed = Schema.Number.check(Schema.isGreaterThanOrEqualTo(0.001)) export const ScrollAcceleration = Schema.Struct({ @@ -17,6 +52,15 @@ export const DiffStyle = Schema.Literals(["auto", "stacked"]).annotate({ description: "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", }) +export const Attention = Schema.Struct({ + enabled: Schema.optional(Schema.Boolean), + notifications: Schema.optional(Schema.Boolean), + sound: Schema.optional(Schema.Boolean), + volume: Schema.optional(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1))), + sound_pack: Schema.optional(Schema.String), + sounds: Schema.optional(TuiAttentionSounds), +}).annotate({ description: "Attention notification and sound settings" }) + export const TuiInfo = Schema.Struct({ $schema: Schema.optional(Schema.String), theme: Schema.optional(Schema.String), @@ -24,6 +68,7 @@ export const TuiInfo = Schema.Struct({ plugin: Schema.optional(Schema.Array(ConfigPlugin.Spec)), plugin_enabled: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), leader_timeout: Schema.optional(KeymapLeaderTimeout), + attention: Schema.optional(Attention), scroll_speed: Schema.optional(ScrollSpeed).annotate({ description: "TUI scroll speed", }), diff --git a/packages/opencode/src/cli/cmd/tui/config/tui.ts b/packages/opencode/src/cli/cmd/tui/config/tui.ts index e53e20d34..562b369db 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui.ts @@ -1,5 +1,6 @@ export * as TuiConfig from "./tui" +import path from "path" import { createBindingLookup } from "@opentui/keymap/extras" import { mergeDeep, unique } from "remeda" import { Context, Effect, Fiber, Layer, Schema } from "effect" @@ -7,7 +8,7 @@ import { ConfigParse } from "@/config/parse" import { InvalidError } from "@/config/error" import * as ConfigPaths from "@/config/paths" import { migrateTuiConfig } from "./tui-migrate" -import { KeymapLeaderTimeoutDefault, TuiInfo } from "./tui-schema" +import { KeymapLeaderTimeoutDefault, resolveAttentionSoundPaths, TuiInfo } from "./tui-schema" import { Flag } from "@opencode-ai/core/flag/flag" import { isRecord } from "@/util/record" import { Global } from "@opencode-ai/core/global" @@ -22,6 +23,7 @@ import * as Log from "@opencode-ai/core/util/log" import { ConfigVariable } from "@/config/variable" import { Npm } from "@opencode-ai/core/npm" import type { DeepMutable } from "@opencode-ai/core/schema" +import type { TuiAttentionSoundName } from "@opencode-ai/plugin/tui" const log = Log.create({ service: "tui.config" }) @@ -33,7 +35,15 @@ type Acc = { plugin_origins: ConfigPlugin.Origin[] } -export type Resolved = Omit & { +export type Resolved = Omit & { + attention: { + enabled: boolean + notifications: boolean + sound: boolean + volume: number + sound_pack: string + sounds: Partial> + } keybinds: TuiKeybind.BindingLookupView leader_timeout: number // Internal resolved plugin list used by runtime loading. @@ -101,7 +111,16 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: }) } } - const validated = ConfigParse.schema(Info, normalized, configFilepath) + const parsed = ConfigParse.schema(Info, normalized, configFilepath) + const validated = parsed.attention?.sounds + ? { + ...parsed, + attention: { + ...parsed.attention, + sounds: resolveAttentionSoundPaths(path.dirname(configFilepath), parsed.attention.sounds), + }, + } + : parsed return yield* resolvePlugins(validated, configFilepath) }).pipe( // catchCause (not tapErrorCause + orElseSucceed) because JSONC parsing and validation @@ -197,6 +216,14 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: const parsedKeybinds = TuiKeybind.parse(keybinds) const result: Resolved = { ...acc.result, + attention: { + enabled: acc.result.attention?.enabled ?? false, + notifications: acc.result.attention?.notifications ?? true, + sound: acc.result.attention?.sound ?? true, + volume: acc.result.attention?.volume ?? 0.4, + sound_pack: acc.result.attention?.sound_pack ?? "opencode.default", + sounds: acc.result.attention?.sounds ?? {}, + }, keybinds: createBindingLookup(TuiKeybind.toBindingConfig(parsedKeybinds), { commandMap: TuiKeybind.CommandMap, bindingDefaults: TuiKeybind.bindingDefaults(), 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 07a2844e9..8c50914df 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 @@ -1,11 +1,52 @@ -import { createMemo, For } from "solid-js" +import type { TuiPluginApi } from "@opencode-ai/plugin/tui" +import { createMemo, For, type Accessor } from "solid-js" import { DEFAULT_THEMES, useTheme } from "@tui/context/theme" import { Flag } from "@opencode-ai/core/flag/flag" +import { useCommandShortcut } from "../../keymap" const themeCount = Object.keys(DEFAULT_THEMES).length -const themeTip = `Use {highlight}/themes{/highlight} or {highlight}Ctrl+X T{/highlight} to switch between ${themeCount} built-in themes` type TipPart = { text: string; highlight: boolean } +type TipShortcut = Accessor +type Shortcuts = { + agentCycle: TipShortcut + childFirst: TipShortcut + childNext: TipShortcut + childPrevious: TipShortcut + commandList: TipShortcut + editorOpen: TipShortcut + helpShow: TipShortcut + inputClear: TipShortcut + inputNewline: TipShortcut + inputPaste: TipShortcut + inputUndo: TipShortcut + leader: TipShortcut + messagesCopy: TipShortcut + messagesFirst: TipShortcut + messagesLast: TipShortcut + messagesPageDown: TipShortcut + messagesPageUp: TipShortcut + messagesToggleConceal: TipShortcut + modelCycleRecent: TipShortcut + modelList: TipShortcut + sessionCycleRecent: TipShortcut + sessionCycleRecentReverse: TipShortcut + sessionExport: TipShortcut + sessionInterrupt: TipShortcut + sessionList: TipShortcut + sessionNew: TipShortcut + sessionParent: TipShortcut + sessionPinToggle: TipShortcut + sessionQuickSwitch1: TipShortcut + sessionQuickSwitch9: TipShortcut + sessionSidebarToggle: TipShortcut + sessionTimeline: TipShortcut + sessionToggleRecent: TipShortcut + statusView: TipShortcut + terminalSuspend: TipShortcut + themeList: TipShortcut +} +type Tip = string | ((shortcuts: Shortcuts) => string | undefined) function parse(tip: string): TipPart[] { const parts: TipPart[] = [] @@ -33,17 +74,86 @@ function parse(tip: string): TipPart[] { const NO_MODELS_TIP = "Run {highlight}/connect{/highlight} to add an AI provider and start coding" -export function Tips(props: { connected?: boolean }) { +function shortcutText(value: string) { + return `{highlight}${value}{/highlight}` +} + +function commandText(command: string, shortcut: string) { + if (!shortcut) return shortcutText(command) + return `${shortcutText(command)} or ${shortcutText(shortcut)}` +} + +function press(shortcut: string, text: string) { + if (!shortcut) return undefined + return `Press ${shortcutText(shortcut)} ${text}` +} + +function configShortcut(api: TuiPluginApi, command: string): TipShortcut { + return () => + api.tuiConfig.keybinds + .get(command) + .map((binding) => api.keys.formatSequence(Array.from(api.keymap.parseKeySequence(binding.key)))) + .filter(Boolean) + .join(", ") +} + +export function Tips(props: { api: TuiPluginApi; connected?: boolean }) { const theme = useTheme().theme - const randomTip = TIPS[Math.floor(Math.random() * TIPS.length)] - const parts = createMemo(() => parse(props.connected === false ? NO_MODELS_TIP : randomTip)) + const tipOffset = Math.random() + const shortcuts: Shortcuts = { + agentCycle: useCommandShortcut("agent.cycle"), + childFirst: configShortcut(props.api, "session.child.first"), + childNext: configShortcut(props.api, "session.child.next"), + childPrevious: configShortcut(props.api, "session.child.previous"), + commandList: useCommandShortcut("command.palette.show"), + editorOpen: useCommandShortcut("prompt.editor"), + helpShow: useCommandShortcut("help.show"), + inputClear: useCommandShortcut("prompt.clear"), + inputNewline: useCommandShortcut("input.newline"), + inputPaste: useCommandShortcut("prompt.paste"), + inputUndo: useCommandShortcut("input.undo"), + leader: configShortcut(props.api, "leader"), + messagesCopy: configShortcut(props.api, "messages.copy"), + messagesFirst: configShortcut(props.api, "session.first"), + messagesLast: configShortcut(props.api, "session.last"), + messagesPageDown: configShortcut(props.api, "session.page.down"), + messagesPageUp: configShortcut(props.api, "session.page.up"), + messagesToggleConceal: configShortcut(props.api, "session.toggle.conceal"), + modelCycleRecent: useCommandShortcut("model.cycle_recent"), + modelList: useCommandShortcut("model.list"), + sessionCycleRecent: useCommandShortcut("session.cycle_recent"), + sessionCycleRecentReverse: useCommandShortcut("session.cycle_recent_reverse"), + sessionExport: configShortcut(props.api, "session.export"), + sessionInterrupt: configShortcut(props.api, "session.interrupt"), + sessionList: useCommandShortcut("session.list"), + sessionNew: useCommandShortcut("session.new"), + sessionParent: configShortcut(props.api, "session.parent"), + sessionPinToggle: configShortcut(props.api, "session.pin.toggle"), + sessionQuickSwitch1: useCommandShortcut("session.quick_switch.1"), + sessionQuickSwitch9: useCommandShortcut("session.quick_switch.9"), + sessionSidebarToggle: configShortcut(props.api, "session.sidebar.toggle"), + sessionTimeline: configShortcut(props.api, "session.timeline"), + sessionToggleRecent: configShortcut(props.api, "session.toggle.recent"), + statusView: useCommandShortcut("opencode.status"), + terminalSuspend: useCommandShortcut("terminal.suspend"), + themeList: useCommandShortcut("theme.switch"), + } + const tip = createMemo(() => { + if (props.connected === false) return NO_MODELS_TIP + const tips = TIPS.flatMap((item) => { + const value = typeof item === "string" ? item : item(shortcuts) + return value ? [value] : [] + }) + return tips[Math.floor(tipOffset * tips.length)] ?? NO_MODELS_TIP + }) + const parts = createMemo(() => parse(tip())) return ( ● Tip{" "} - + {(part) => {part.text}} @@ -52,46 +162,66 @@ export function Tips(props: { connected?: boolean }) { ) } -const TIPS = [ +const TIPS: Tip[] = [ "Type {highlight}@{/highlight} followed by a filename to fuzzy search and attach files", "Start a message with {highlight}!{/highlight} to run shell commands directly (e.g., {highlight}!ls -la{/highlight})", - "Press {highlight}Tab{/highlight} to cycle between Build and Plan agents", + (shortcuts) => press(shortcuts.agentCycle(), "to cycle between Build and Plan agents"), "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 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", + (shortcuts) => press(shortcuts.inputPaste(), "to paste images from your clipboard into the prompt"), + (shortcuts) => `Use ${commandText("/editor", shortcuts.editorOpen())} to compose messages in your external editor`, "Run {highlight}/init{/highlight} to auto-generate project rules based on your codebase", - "Run {highlight}/models{/highlight} or {highlight}Ctrl+X M{/highlight} to see and switch between available AI models", - themeTip, - "Press {highlight}Ctrl+X N{/highlight} or {highlight}/new{/highlight} to start a fresh conversation session", - "Use {highlight}/sessions{/highlight} or {highlight}Ctrl+X L{/highlight} to list and continue previous conversations", + (shortcuts) => `Use ${commandText("/models", shortcuts.modelList())} to see and switch between available AI models`, + (shortcuts) => `Use ${commandText("/themes", shortcuts.themeList())} to switch between ${themeCount} built-in themes`, + (shortcuts) => `Use ${commandText("/new", shortcuts.sessionNew())} to start a fresh conversation session`, + (shortcuts) => `Use ${commandText("/sessions", shortcuts.sessionList())} to list and continue previous conversations`, ...(Flag.OPENCODE_EXPERIMENTAL_SESSION_SWITCHING - ? [ - "Press {highlight}Ctrl+F{/highlight} in the session list to pin a session so it stays at the top", - "Pinned and recent sessions are bound to {highlight}Ctrl+X 1{/highlight} through {highlight}Ctrl+X 9{/highlight} for one-press switching", - "Press {highlight}Ctrl+X ]{/highlight} / {highlight}Ctrl+X [{/highlight} to cycle through recently visited sessions", - "Press {highlight}Ctrl+H{/highlight} in the session list to show or hide a session in the Recent group", - ] + ? ([ + (shortcuts) => + press(shortcuts.sessionPinToggle(), "in the session list to pin a session so it stays at the top"), + (shortcuts) => + shortcuts.sessionQuickSwitch1() && shortcuts.sessionQuickSwitch9() + ? `Pinned and recent sessions are bound to ${shortcutText(shortcuts.sessionQuickSwitch1())} through ${shortcutText(shortcuts.sessionQuickSwitch9())} for one-press switching` + : undefined, + (shortcuts) => + shortcuts.sessionCycleRecent() && shortcuts.sessionCycleRecentReverse() + ? `Press ${shortcutText(shortcuts.sessionCycleRecent())} / ${shortcutText(shortcuts.sessionCycleRecentReverse())} to cycle through recently visited sessions` + : undefined, + (shortcuts) => + press(shortcuts.sessionToggleRecent(), "in the session list to show or hide a session in the Recent group"), + ] satisfies Tip[]) : []), "Run {highlight}/compact{/highlight} to summarize long sessions near context limits", - "Press {highlight}Ctrl+X X{/highlight} or {highlight}/export{/highlight} to save the conversation as Markdown", - "Press {highlight}Ctrl+X Y{/highlight} to copy the assistant's last message to clipboard", - "Press {highlight}Ctrl+P{/highlight} to see all available actions and commands", + (shortcuts) => `Use ${commandText("/export", shortcuts.sessionExport())} to save the conversation as Markdown`, + (shortcuts) => press(shortcuts.messagesCopy(), "to copy the assistant's last message to clipboard"), + (shortcuts) => press(shortcuts.commandList(), "to see all available actions and commands"), "Run {highlight}/connect{/highlight} to add API keys for 75+ supported LLM providers", - "The leader key is {highlight}Ctrl+X{/highlight}; combine with other keys for quick actions", - "Press {highlight}F2{/highlight} to quickly switch between recently used models", - "Press {highlight}Ctrl+X B{/highlight} to show/hide the sidebar panel", - "Use {highlight}PageUp{/highlight}/{highlight}PageDown{/highlight} to navigate through conversation history", - "Press {highlight}Ctrl+G{/highlight} or {highlight}Home{/highlight} to jump to the beginning of the conversation", - "Press {highlight}Ctrl+Alt+G{/highlight} or {highlight}End{/highlight} to jump to the most recent message", - "Press {highlight}Shift+Enter{/highlight} or {highlight}Ctrl+J{/highlight} to add newlines in your prompt", - "Press {highlight}Ctrl+C{/highlight} when typing to clear the input field", - "Press {highlight}Escape{/highlight} to stop the AI mid-response", + (shortcuts) => `The leader key is ${shortcutText(shortcuts.leader())}; combine with other keys for quick actions`, + (shortcuts) => press(shortcuts.modelCycleRecent(), "to quickly switch between recently used models"), + (shortcuts) => press(shortcuts.sessionSidebarToggle(), "in a session to show or hide the sidebar panel"), + (shortcuts) => + shortcuts.messagesPageUp() && shortcuts.messagesPageDown() + ? `Use ${shortcutText(shortcuts.messagesPageUp())}/${shortcutText(shortcuts.messagesPageDown())} to navigate through conversation history` + : undefined, + (shortcuts) => press(shortcuts.messagesFirst(), "to jump to the beginning of the conversation"), + (shortcuts) => press(shortcuts.messagesLast(), "to jump to the most recent message"), + (shortcuts) => press(shortcuts.inputNewline(), "to add newlines in your prompt"), + (shortcuts) => press(shortcuts.inputClear(), "when typing to clear the input field"), + (shortcuts) => press(shortcuts.sessionInterrupt(), "to stop the AI mid-response"), "Switch to {highlight}Plan{/highlight} agent to get suggestions without making actual changes", "Use {highlight}@agent-name{/highlight} in prompts to invoke specialized subagents", - "Press {highlight}Ctrl+X Right/Left{/highlight} to cycle through parent and child sessions", + (shortcuts) => { + const items = [ + shortcuts.sessionParent(), + shortcuts.childFirst(), + shortcuts.childPrevious(), + shortcuts.childNext(), + ].filter(Boolean) + if (!items.length) return undefined + return `Use ${items.map(shortcutText).join(" / ")} to move between parent and child sessions` + }, "Create {highlight}opencode.json{/highlight} for server settings and {highlight}tui.json{/highlight} for TUI settings", "Place TUI settings in {highlight}~/.config/opencode/tui.json{/highlight} for global config", "Add {highlight}$schema{/highlight} to your config for autocomplete in your editor", @@ -99,22 +229,21 @@ const TIPS = [ "Override any keybind in {highlight}tui.json{/highlight} via the {highlight}keybinds{/highlight} section", "Set any keybind to {highlight}none{/highlight} to disable it completely", "Configure local or remote MCP servers in the {highlight}mcp{/highlight} config section", - "OpenCode auto-handles OAuth for remote MCP servers requiring auth", - "Add {highlight}.md{/highlight} files to {highlight}.opencode/command/{/highlight} to define reusable custom prompts", + "Add {highlight}.md{/highlight} files to {highlight}.opencode/commands/{/highlight} to define reusable custom prompts", "Use {highlight}$ARGUMENTS{/highlight}, {highlight}$1{/highlight}, {highlight}$2{/highlight} in custom commands for dynamic input", "Use backticks in commands to inject shell output (e.g., {highlight}`git status`{/highlight})", - "Add {highlight}.md{/highlight} files to {highlight}.opencode/agent/{/highlight} for specialized AI personas", + "Add {highlight}.md{/highlight} files to {highlight}.opencode/agents/{/highlight} for specialized AI personas", "Configure per-agent permissions for {highlight}edit{/highlight}, {highlight}bash{/highlight}, and {highlight}webfetch{/highlight} tools", 'Use patterns like {highlight}"git *": "allow"{/highlight} for granular bash permissions', 'Set {highlight}"rm -rf *": "deny"{/highlight} to block destructive commands', 'Configure {highlight}"git push": "ask"{/highlight} to require approval before pushing', - "OpenCode auto-formats files using prettier, gofmt, ruff, and more", - 'Set {highlight}"formatter": false{/highlight} in config to disable all auto-formatting', + 'Set {highlight}"formatter": true{/highlight} in config to enable built-in formatters like prettier, gofmt, and ruff', + 'Set {highlight}"formatter": false{/highlight} in config to disable formatters enabled by another config layer', "Define custom formatter commands with file extensions in config", - "OpenCode uses LSP servers for intelligent code analysis", + 'Set {highlight}"lsp": true{/highlight} in config to enable built-in LSP servers for code analysis', "Create {highlight}.ts{/highlight} files in {highlight}.opencode/tools/{/highlight} to define new LLM tools", "Tool definitions can invoke scripts written in Python, Go, etc", - "Add {highlight}.ts{/highlight} files to {highlight}.opencode/plugin/{/highlight} for event hooks", + "Add {highlight}.ts{/highlight} files to {highlight}.opencode/plugins/{/highlight} for event hooks", "Use plugins to send OS notifications when sessions complete", "Create a plugin to prevent OpenCode from reading sensitive files", "Use {highlight}opencode run{/highlight} for non-interactive scripting", @@ -133,7 +262,7 @@ const TIPS = [ 'Use {highlight}"theme": "system"{/highlight} to match your terminal\'s colors', "Create JSON theme files in {highlight}.opencode/themes/{/highlight} directory", "Themes support dark/light variants for both modes", - "Reference ANSI colors 0-255 in custom themes", + "Use numeric xterm color codes 0-255 in custom theme JSON", "Use {highlight}{env:VAR_NAME}{/highlight} syntax to reference environment variables in config", "Use {highlight}{file:path}{/highlight} to include file contents in config values", "Use {highlight}instructions{/highlight} in config to load additional rules files", @@ -149,18 +278,23 @@ const TIPS = [ "Permission {highlight}external_directory{/highlight} protects files outside project", "Run {highlight}opencode debug config{/highlight} to troubleshoot configuration", "Use {highlight}--print-logs{/highlight} flag to see detailed logs in stderr", - "Press {highlight}Ctrl+X G{/highlight} or {highlight}/timeline{/highlight} to jump to specific messages", - "Press {highlight}Ctrl+X H{/highlight} to toggle code block visibility in messages", - "Press {highlight}Ctrl+X S{/highlight} or {highlight}/status{/highlight} to see system status info", + (shortcuts) => `Use ${commandText("/timeline", shortcuts.sessionTimeline())} to jump to specific messages`, + (shortcuts) => press(shortcuts.messagesToggleConceal(), "to toggle code block visibility in messages"), + (shortcuts) => `Use ${commandText("/status", shortcuts.statusView())} to see system status info`, "Enable {highlight}scroll_acceleration{/highlight} in {highlight}tui.json{/highlight} for smooth macOS-style scrolling", - "Toggle username display in chat via command palette ({highlight}Ctrl+P{/highlight})", + (shortcuts) => + shortcuts.commandList() + ? `Toggle username display in chat via the command palette (${shortcutText(shortcuts.commandList())})` + : "Toggle username display in chat via the command palette", "Run {highlight}docker run -it --rm ghcr.io/anomalyco/opencode{/highlight} for containerized use", "Use {highlight}/connect{/highlight} with OpenCode Zen for curated, tested models", "Commit your project's {highlight}AGENTS.md{/highlight} file to Git for team sharing", "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", + (shortcuts) => `Use ${commandText("/help", shortcuts.helpShow())} to show the help dialog`, "Use {highlight}/rename{/highlight} to rename the current session", ...(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"]), + ? ([(shortcuts) => press(shortcuts.inputUndo(), "to undo changes in your prompt")] satisfies Tip[]) + : ([ + (shortcuts) => press(shortcuts.terminalSuspend(), "to suspend the terminal and return to your shell"), + ] satisfies Tip[])), ] diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx index 69071b1f7..598366c08 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx @@ -24,9 +24,9 @@ function View(props: { api: TuiPluginApi; hidden: boolean; show: boolean; connec })) return ( - + - + ) diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/notifications.ts b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/notifications.ts new file mode 100644 index 000000000..cda815f5f --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/notifications.ts @@ -0,0 +1,94 @@ +import type { Event } from "@opencode-ai/sdk/v2" +import type { TuiAttentionSoundName, TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui" +import type { InternalTuiPlugin } from "../../plugin/internal" + +const id = "internal:notifications" + +type SessionError = Extract["properties"]["error"] + +function notify(api: TuiPluginApi, sessionID: string | undefined, message: string, sound: TuiAttentionSoundName) { + const session = sessionID ? api.state.session.get(sessionID) : undefined + const isSubagent = session?.parentID !== undefined + void api.attention.notify({ + title: session?.title, + message, + notification: isSubagent ? false : { when: "blurred" }, + sound: { name: sound, when: "always" }, + }) +} + +function sessionErrorMessage(error: SessionError) { + if (error?.name === "MessageAbortedError") return "Session aborted" + const data = error?.data + if (data && typeof data === "object" && "message" in data && data.message === "SSE read timed out") { + return "Model stopped responding" + } + return "Session error" +} + +const tui: TuiPlugin = async (api) => { + const active = new Set() + const errored = new Set() + const questions = new Set() + const permissions = new Set() + + api.event.on("question.asked", (event) => { + if (questions.has(event.properties.id)) return + questions.add(event.properties.id) + notify(api, event.properties.sessionID, "Question needs input", "question") + }) + + api.event.on("question.replied", (event) => { + questions.delete(event.properties.requestID) + }) + + api.event.on("question.rejected", (event) => { + questions.delete(event.properties.requestID) + }) + + api.event.on("permission.asked", (event) => { + if (permissions.has(event.properties.id)) return + permissions.add(event.properties.id) + notify(api, event.properties.sessionID, "Permission needs input", "permission") + }) + + api.event.on("permission.replied", (event) => { + permissions.delete(event.properties.requestID) + }) + + api.event.on("session.status", (event) => { + const sessionID = event.properties.sessionID + if (event.properties.status.type === "busy" || event.properties.status.type === "retry") { + active.add(sessionID) + errored.delete(sessionID) + return + } + + if (event.properties.status.type !== "idle") return + if (!active.has(sessionID)) return + active.delete(sessionID) + + if (errored.has(sessionID)) { + errored.delete(sessionID) + return + } + + const session = api.state.session.get(sessionID) + notify(api, sessionID, "Session done", session?.parentID ? "subagent_done" : "done") + }) + + api.event.on("session.error", (event) => { + const sessionID = event.properties.sessionID + if (!sessionID) return + if (!active.has(sessionID)) return + errored.add(sessionID) + notify(api, sessionID, sessionErrorMessage(event.properties.error), "error") + }) +} + +const plugin: InternalTuiPlugin = { + id, + tui, +} + +export default plugin diff --git a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx index 8958a9285..05bfa31d1 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx +++ b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx @@ -40,6 +40,7 @@ type Input = { theme: ReturnType toast: ReturnType renderer: TuiPluginApi["renderer"] + attention: TuiPluginApi["attention"] } function routeRegister(routes: RouteMap, list: TuiRouteDefinition[], bump: () => void) { @@ -206,6 +207,7 @@ export function createTuiApi(input: Input): TuiPluginApi { } return { app: appApi(), + attention: input.attention, // Keep deprecated `api.command` working for v1 plugins; remove in v2. command: createCommandShim(input.keymap, input.dialog, input.tuiConfig.keybinds), keys: { diff --git a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts index 664b5c1ac..eaa9dfb32 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts @@ -7,6 +7,7 @@ import SidebarTodo from "../feature-plugins/sidebar/todo" import SidebarFiles from "../feature-plugins/sidebar/files" import SidebarFooter from "../feature-plugins/sidebar/footer" import PluginManager from "../feature-plugins/system/plugins" +import Notifications from "../feature-plugins/system/notifications" import SessionV2Debug from "../feature-plugins/system/session-v2" import WhichKey from "../feature-plugins/system/which-key" import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui" @@ -27,6 +28,7 @@ export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [ SidebarTodo, SidebarFiles, SidebarFooter, + Notifications, PluginManager, WhichKey, ...(Flag.OPENCODE_EXPERIMENTAL_EVENT_SYSTEM ? [SessionV2Debug] : []), diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index dad4595e7..4af16d2b8 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -17,6 +17,7 @@ import { TuiConfig } from "@/cli/cmd/tui/config/tui" import * as Log from "@opencode-ai/core/util/log" import { errorData, errorMessage } from "@/util/error" import { isRecord } from "@/util/record" +import { resolveAttentionSoundPaths } from "../config/tui-schema" import { readPackageThemes, readPluginId, @@ -51,7 +52,7 @@ type PluginLoad = { id: string module: TuiPluginModule origin: ConfigPlugin.Origin - theme_root: string + plugin_root: string theme_files: string[] } @@ -106,6 +107,7 @@ const ScopedKeymapMethods = new Set([ type RuntimeState = { directory: string api: Api + dispose?: () => void slots: HostSlots plugins: PluginEntry[] plugins_by_id: Map @@ -156,6 +158,37 @@ function createScopedKeymap(keymap: TuiPluginApi["keymap"], scope: PluginScope): }) } +function createScopedAttention( + attention: TuiPluginApi["attention"], + scope: PluginScope, + root: string, +): TuiPluginApi["attention"] { + return { + notify(input) { + return attention.notify(input) + }, + soundboard: { + registerPack(pack) { + return scope.track( + attention.soundboard.registerPack({ + ...pack, + sounds: resolveAttentionSoundPaths(root, pack.sounds, { trim: true }), + }), + ) + }, + activate(id, options) { + return attention.soundboard.activate(id, options) + }, + current() { + return attention.soundboard.current() + }, + list() { + return attention.soundboard.list() + }, + }, + } +} + type CleanupResult = { type: "ok" } | { type: "error"; error: unknown } | { type: "timeout" } function runCleanup(fn: () => unknown, ms: number): Promise { @@ -204,8 +237,7 @@ function createThemeInstaller( plugin: PluginEntry, ): TuiTheme["install"] { return async (file) => { - const raw = file.startsWith("file://") ? fileURLToPath(file) : file - const src = path.isAbsolute(raw) ? raw : path.resolve(root, raw) + const src = Filesystem.resolveFilePath(root, file) const name = path.basename(src, path.extname(src)) const source_dir = path.dirname(meta.source) const local_dir = @@ -330,7 +362,7 @@ function loadInternalPlugin(item: InternalTuiPlugin): PluginLoad { scope: "global", source: target, }, - theme_root: process.cwd(), + plugin_root: process.cwd(), theme_files: [], } } @@ -352,7 +384,7 @@ async function readThemeFiles(spec: string, pkg?: PluginPackage) { async function syncPluginThemes(plugin: PluginEntry) { if (!plugin.load.theme_files.length) return if (plugin.meta.state === "same") return - const install = createThemeInstaller(plugin.load.origin, plugin.load.theme_root, plugin.load.spec, plugin) + const install = createThemeInstaller(plugin.load.origin, plugin.load.plugin_root, plugin.load.spec, plugin) for (const file of plugin.load.theme_files) { await install(file).catch((error) => { warn("failed to sync tui plugin oc-themes", { path: plugin.load.spec, id: plugin.id, theme: file, error }) @@ -552,7 +584,7 @@ function pluginApi(runtime: RuntimeState, plugin: PluginEntry, scope: PluginScop } const theme: TuiPluginApi["theme"] = Object.assign(Object.create(api.theme), { - install: createThemeInstaller(load.origin, load.theme_root, load.spec, plugin), + install: createThemeInstaller(load.origin, load.plugin_root, load.spec, plugin), }) const event: TuiPluginApi["event"] = { @@ -576,6 +608,7 @@ function pluginApi(runtime: RuntimeState, plugin: PluginEntry, scope: PluginScop return { app: api.app, + attention: createScopedAttention(api.attention, scope, load.plugin_root), // Keep deprecated `api.command` working for v1 plugins; remove in v2. command: createCommandShim(keymap, api.ui.dialog, api.tuiConfig.keybinds), keys: api.keys, @@ -682,7 +715,7 @@ async function resolveExternalPlugins(list: ConfigPlugin.Origin[], wait: () => P id, module: mod, origin, - theme_root: loaded.pkg?.dir ?? resolveRoot(loaded.target), + plugin_root: loaded.pkg?.dir ?? resolveRoot(loaded.target), theme_files, } }, @@ -709,7 +742,7 @@ async function resolveExternalPlugins(list: ConfigPlugin.Origin[], wait: () => P id, module: EMPTY_TUI, origin, - theme_root: loaded.pkg?.dir ?? resolveRoot(loaded.target), + plugin_root: loaded.pkg?.dir ?? resolveRoot(loaded.target), theme_files, } }, @@ -967,7 +1000,7 @@ let loaded: Promise | undefined let runtime: RuntimeState | undefined export const Slot = View -export async function init(input: { api: HostPluginApi; config: TuiConfig.Resolved }) { +export async function init(input: { api: HostPluginApi; config: TuiConfig.Resolved; dispose?: () => void }) { const cwd = process.cwd() if (loaded) { if (dir !== cwd) { @@ -1014,15 +1047,17 @@ export async function dispose() { for (const plugin of queue) { await deactivatePluginEntry(state, plugin, false) } + state.dispose?.() } -async function load(input: { api: Api; config: TuiConfig.Resolved }) { +async function load(input: { api: Api; config: TuiConfig.Resolved; dispose?: () => void }) { const { api, config } = input const cwd = process.cwd() const slots = setupSlots(api) const next: RuntimeState = { directory: cwd, api, + dispose: input.dispose, slots, plugins: [], plugins_by_id: new Map(), diff --git a/packages/opencode/src/cli/cmd/tui/util/audio.ts b/packages/opencode/src/cli/cmd/tui/util/audio.ts new file mode 100644 index 000000000..7d7c3d5a4 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/util/audio.ts @@ -0,0 +1,58 @@ +import { Audio, type AudioErrorContext, type AudioPlayOptions, type AudioSound, type AudioVoice } from "@opentui/core" +import * as Log from "@opencode-ai/core/util/log" + +const log = Log.create({ service: "tui.audio" }) + +let audio: Audio | null | undefined +const sounds = new Map>() + +function getAudio() { + if (audio !== undefined) return audio + try { + const next = Audio.create({ autoStart: false }) + next.on("error", (error: Error, context: AudioErrorContext) => { + log.debug("tui audio error", { error, context }) + }) + audio = next + return next + } catch (error) { + log.debug("failed to create tui audio", { error }) + audio = null + return null + } +} + +export function loadSoundFile(file: string) { + const current = getAudio() + if (!current) return Promise.resolve(null) + const cached = sounds.get(file) + if (cached) return cached + const task = Bun.file(file) + .bytes() + .then((bytes) => current.loadSound(bytes)) + .catch((error) => { + log.debug("failed to load tui sound", { file, error }) + return null + }) + sounds.set(file, task) + return task +} + +export function play(sound: AudioSound, options?: AudioPlayOptions) { + const current = getAudio() + if (!current) return null + if (!current.isStarted() && !current.start()) return null + return current.play(sound, options) +} + +export function stopVoice(voice: AudioVoice) { + return audio?.stopVoice(voice) ?? false +} + +export function dispose() { + audio?.dispose() + audio = undefined + sounds.clear() +} + +export * as TuiAudio from "./audio" diff --git a/packages/opencode/src/util/filesystem.ts b/packages/opencode/src/util/filesystem.ts index ded2b4517..696603adb 100644 --- a/packages/opencode/src/util/filesystem.ts +++ b/packages/opencode/src/util/filesystem.ts @@ -1,10 +1,11 @@ import { chmod, mkdir, readFile, stat as statFile, writeFile } from "fs/promises" import { createWriteStream, existsSync, statSync } from "fs" import { realpathSync } from "fs" -import { dirname, join, relative, resolve as pathResolve, win32 } from "path" +import { dirname, isAbsolute, join, relative, resolve as pathResolve, win32 } from "path" import { Readable } from "stream" import { pipeline } from "stream/promises" import { Glob } from "@opencode-ai/core/util/glob" +import { fileURLToPath } from "url" // Fast sync version for metadata checks export async function exists(p: string): Promise { @@ -142,6 +143,12 @@ export function resolve(p: string): string { } } +export function resolveFilePath(root: string, file: string): string { + const raw = file.startsWith("file://") ? fileURLToPath(file) : file + if (isAbsolute(raw)) return raw + return pathResolve(root, raw) +} + export function windowsPath(p: string): string { if (process.platform !== "win32") return p return ( diff --git a/packages/opencode/test/cli/cmd/tui/attention.test.ts b/packages/opencode/test/cli/cmd/tui/attention.test.ts new file mode 100644 index 000000000..071aabdd7 --- /dev/null +++ b/packages/opencode/test/cli/cmd/tui/attention.test.ts @@ -0,0 +1,484 @@ +import { describe, expect, test } from "bun:test" +import type { AudioPlayOptions, AudioSound } from "@opentui/core" +import { createTuiAttention } from "@/cli/cmd/tui/attention" +import type { TuiConfig } from "@/cli/cmd/tui/config/tui" + +type FocusEvent = "focus" | "blur" + +type AttentionConfig = Pick + +class FakeRenderer { + isDestroyed = false + notificationResult = true + notificationThrows = false + notifications: { message: string; title: string | undefined }[] = [] + listeners: Record void>> = { + focus: new Set(), + blur: new Set(), + } + + on(event: FocusEvent, listener: () => void) { + this.listeners[event].add(listener) + return this + } + + off(event: FocusEvent, listener: () => void) { + this.listeners[event].delete(listener) + return this + } + + emit(event: FocusEvent) { + for (const listener of this.listeners[event]) listener() + } + + listenerCount(event: FocusEvent) { + return this.listeners[event].size + } + + triggerNotification(message: string, title?: string) { + if (this.notificationThrows) throw new Error("notification failed") + this.notifications.push({ message, title }) + return this.notificationResult + } +} + +class FakeAudioEngine { + loadResult: AudioSound | null = 1 + playResult: number | null = 1 + loadCalls = 0 + playCalls = 0 + volumes: (number | undefined)[] = [] + loadPaths: string[] = [] + rejectLoad = false + rejectPaths = new Set() + + async loadSoundFile(path: string) { + this.loadCalls += 1 + this.loadPaths.push(path) + if (this.rejectLoad || this.rejectPaths.has(path)) throw new Error("decode failed") + return this.loadResult + } + + play(_sound: AudioSound, options?: AudioPlayOptions) { + this.playCalls += 1 + this.volumes.push(options?.volume) + return this.playResult + } +} + +class FakeKV { + store: Record = {} + + get ready() { + return true + } + + get(key: string, fallback?: Value) { + return (this.store[key] ?? fallback) as Value + } + + set(key: string, value: unknown) { + this.store[key] = value + } +} + +function config(attention: Partial = {}): AttentionConfig { + return { + attention: { + enabled: true, + notifications: true, + sound: true, + volume: 0.4, + sound_pack: "opencode.default", + sounds: {}, + ...attention, + }, + } +} + +describe("createTuiAttention", () => { + test("defaults to sound always and notification blurred", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + + expect(await attention.notify({ message: "hello" })).toEqual({ + ok: true, + notification: false, + sound: true, + }) + expect(renderer.notifications).toHaveLength(0) + expect(audio.playCalls).toBe(1) + }) + + test("supports blurred-only requests", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + + expect(await attention.notify({ message: "unknown", sound: { when: "blurred" } })).toEqual({ + ok: false, + notification: false, + sound: false, + skipped: "focus_unknown", + }) + renderer.emit("focus") + expect(await attention.notify({ message: "focused", sound: { when: "blurred" } })).toEqual({ + ok: false, + notification: false, + sound: false, + skipped: "focused", + }) + renderer.emit("blur") + expect(await attention.notify({ message: "blurred", sound: { when: "blurred" } })).toEqual({ + ok: true, + notification: true, + sound: true, + }) + expect(audio.playCalls).toBe(1) + }) + + test("supports focused-only requests", async () => { + const renderer = new FakeRenderer() + const attention = createTuiAttention({ renderer, config: config(), audio: new FakeAudioEngine() }) + + expect(await attention.notify({ message: "unknown", notification: { when: "focused" }, sound: false })).toEqual({ + ok: false, + notification: false, + sound: false, + skipped: "focus_unknown", + }) + renderer.emit("blur") + expect(await attention.notify({ message: "blurred", notification: { when: "focused" }, sound: false })).toEqual({ + ok: false, + notification: false, + sound: false, + skipped: "blurred", + }) + renderer.emit("focus") + expect(await attention.notify({ message: "focused", notification: { when: "focused" }, sound: false })).toEqual({ + ok: true, + notification: true, + sound: false, + }) + expect(renderer.notifications).toEqual([{ title: "opencode", message: "focused" }]) + }) + + test("notification can deliver while focused when requested", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + renderer.emit("focus") + + expect(await attention.notify({ message: "hello", notification: { when: "always" } })).toEqual({ + ok: true, + notification: true, + sound: true, + }) + expect(audio.playCalls).toBe(1) + expect(renderer.notifications).toEqual([{ title: "opencode", message: "hello" }]) + }) + + test("notifies while blurred", async () => { + const renderer = new FakeRenderer() + const attention = createTuiAttention({ renderer, config: config(), audio: new FakeAudioEngine() }) + renderer.emit("blur") + + expect(await attention.notify({ title: "opencode", message: "hello", sound: false })).toEqual({ + ok: true, + notification: true, + sound: false, + }) + expect(renderer.notifications).toEqual([{ title: "opencode", message: "hello" }]) + }) + + test("when requested, blurred-only calls do not notify or play sound while focused", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + renderer.emit("focus") + + expect(await attention.notify({ message: "hello", sound: { when: "blurred" } })).toEqual({ + ok: false, + notification: false, + sound: false, + skipped: "focused", + }) + expect(renderer.notifications).toHaveLength(0) + expect(audio.loadCalls).toBe(0) + }) + + test("can play sound always while notification is blurred-only", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + renderer.emit("focus") + + expect( + await attention.notify({ + message: "hello", + sound: { name: "question" }, + }), + ).toEqual({ + ok: true, + notification: false, + sound: true, + }) + expect(renderer.notifications).toHaveLength(0) + expect(audio.playCalls).toBe(1) + + renderer.emit("blur") + expect( + await attention.notify({ + message: "hello again", + sound: { name: "question" }, + }), + ).toEqual({ + ok: true, + notification: true, + sound: true, + }) + expect(renderer.notifications).toEqual([{ title: "opencode", message: "hello again" }]) + }) + + test("can disable notification per call while still playing sound", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + + expect(await attention.notify({ message: "hello", notification: false })).toEqual({ + ok: true, + notification: false, + sound: true, + }) + expect(renderer.notifications).toHaveLength(0) + expect(audio.playCalls).toBe(1) + }) + + test("skips empty messages and disabled attention", async () => { + const empty = new FakeRenderer() + empty.emit("blur") + const disabled = new FakeRenderer() + disabled.emit("blur") + + expect(await createTuiAttention({ renderer: empty, config: config() }).notify({ message: " \n " })).toEqual({ + ok: false, + notification: false, + sound: false, + skipped: "empty_message", + }) + expect( + await createTuiAttention({ renderer: disabled, config: config({ enabled: false }) }).notify({ message: "hello" }), + ).toEqual({ + ok: false, + notification: false, + sound: false, + skipped: "attention_disabled", + }) + }) + + test("respects notification and sound config independently", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config({ notifications: false }), audio }) + renderer.emit("blur") + + expect(await attention.notify({ message: "hello", sound: true })).toEqual({ + ok: true, + notification: false, + sound: true, + }) + expect(renderer.notifications).toHaveLength(0) + expect(audio.playCalls).toBe(1) + + const soundDisabledRenderer = new FakeRenderer() + const soundDisabledAudio = new FakeAudioEngine() + const soundDisabled = createTuiAttention({ + renderer: soundDisabledRenderer, + config: config({ sound: false }), + audio: soundDisabledAudio, + }) + soundDisabledRenderer.emit("blur") + + expect(await soundDisabled.notify({ message: "hello", sound: true })).toEqual({ + ok: true, + notification: true, + sound: false, + }) + expect(soundDisabledAudio.loadCalls).toBe(0) + }) + + test("loads audio lazily only for eligible sound requests", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + + await attention.notify({ message: "unknown", sound: { when: "blurred" } }) + expect(audio.loadCalls).toBe(0) + + renderer.emit("blur") + expect(await attention.notify({ message: "blurred", sound: { volume: 2 } })).toEqual({ + ok: true, + notification: true, + sound: true, + }) + expect(audio.loadCalls).toBe(1) + expect(audio.volumes).toEqual([1]) + }) + + test("handles unavailable playback and delegates sound loading", async () => { + const unavailableRenderer = new FakeRenderer() + const unavailableAudio = new FakeAudioEngine() + unavailableAudio.playResult = null + const unavailable = createTuiAttention({ renderer: unavailableRenderer, config: config(), audio: unavailableAudio }) + unavailableRenderer.emit("blur") + + expect(await unavailable.notify({ message: "hello", sound: true })).toEqual({ + ok: true, + notification: true, + sound: false, + }) + expect(unavailableAudio.loadCalls).toBe(1) + expect(unavailableAudio.playCalls).toBe(1) + + const repeatedRenderer = new FakeRenderer() + const repeatedAudio = new FakeAudioEngine() + const repeated = createTuiAttention({ renderer: repeatedRenderer, config: config(), audio: repeatedAudio }) + repeatedRenderer.emit("blur") + + await repeated.notify({ message: "one", sound: true }) + await repeated.notify({ message: "two", sound: true }) + expect(repeatedAudio.loadCalls).toBe(2) + expect(repeatedAudio.playCalls).toBe(2) + }) + + test("plays named sounds from the active sound pack", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + renderer.emit("blur") + + const dispose = attention.soundboard.registerPack({ + id: "acme.soft", + name: "Soft Alerts", + sounds: { + question: "/tmp/question.mp3", + }, + }) + + expect(attention.soundboard.activate("acme.soft")).toBe(true) + expect(attention.soundboard.current()).toBe("acme.soft") + expect(attention.soundboard.list()).toContainEqual({ + id: "acme.soft", + name: "Soft Alerts", + active: true, + builtin: false, + }) + + expect(await attention.notify({ message: "question", sound: { name: "question" } })).toEqual({ + ok: true, + notification: true, + sound: true, + }) + expect(audio.loadPaths).toEqual(["/tmp/question.mp3"]) + + dispose() + expect(attention.soundboard.current()).toBe("opencode.default") + }) + + test("uses config sound overrides before active pack sounds and falls back on load failure", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + audio.rejectPaths.add("/tmp/bad-question.mp3") + const attention = createTuiAttention({ + renderer, + config: config({ sounds: { question: "/tmp/bad-question.mp3" } }), + audio, + }) + renderer.emit("blur") + + attention.soundboard.registerPack({ + id: "acme.soft", + sounds: { + question: "/tmp/good-question.mp3", + }, + }) + attention.soundboard.activate("acme.soft") + + expect(await attention.notify({ message: "question", sound: { name: "question" } })).toEqual({ + ok: true, + notification: true, + sound: true, + }) + expect(audio.loadPaths).toEqual(["/tmp/bad-question.mp3", "/tmp/good-question.mp3"]) + }) + + test("persists activated sound pack in KV", () => { + const kv = new FakeKV() + const renderer = new FakeRenderer() + const attention = createTuiAttention({ renderer, config: config(), kv }) + + attention.soundboard.registerPack({ id: "acme.soft", sounds: { done: "/tmp/done.mp3" } }) + + expect(attention.soundboard.activate("missing", { persist: true })).toBe(false) + expect(kv.store.attention_sound_pack).toBeUndefined() + expect(attention.soundboard.activate("acme.soft", { persist: true })).toBe(true) + expect(kv.store.attention_sound_pack).toBe("acme.soft") + + const next = createTuiAttention({ renderer: new FakeRenderer(), config: config(), kv }) + next.soundboard.registerPack({ id: "acme.soft", sounds: { done: "/tmp/done.mp3" } }) + expect(next.soundboard.current()).toBe("acme.soft") + }) + + test("does not throw for notification or sound failures", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + renderer.notificationThrows = true + audio.rejectLoad = true + const attention = createTuiAttention({ renderer, config: config(), audio }) + renderer.emit("blur") + + expect(await attention.notify({ message: "hello", sound: true })).toEqual({ + ok: false, + notification: false, + sound: false, + }) + }) + + test("strips unsafe notification text", async () => { + const renderer = new FakeRenderer() + const attention = createTuiAttention({ renderer, config: config(), audio: new FakeAudioEngine() }) + renderer.emit("blur") + + await attention.notify({ + title: "\u001b[31m danger\n title\u0007", + message: "\u001b[32m hello\n world\u0000", + }) + + expect(renderer.notifications).toEqual([{ title: "danger title", message: "hello world" }]) + }) + + test("disposes renderer listeners", async () => { + const renderer = new FakeRenderer() + const audio = new FakeAudioEngine() + const attention = createTuiAttention({ renderer, config: config(), audio }) + renderer.emit("blur") + await attention.notify({ message: "hello", sound: true }) + + expect(renderer.listenerCount("focus")).toBe(1) + expect(renderer.listenerCount("blur")).toBe(1) + + attention.dispose() + renderer.isDestroyed = true + + expect(renderer.listenerCount("focus")).toBe(0) + expect(renderer.listenerCount("blur")).toBe(0) + expect(audio.loadCalls).toBe(1) + expect(await attention.notify({ message: "hello" })).toEqual({ + ok: false, + notification: false, + sound: false, + skipped: "renderer_destroyed", + }) + }) +}) diff --git a/packages/opencode/test/cli/cmd/tui/notifications.test.ts b/packages/opencode/test/cli/cmd/tui/notifications.test.ts new file mode 100644 index 000000000..17ed54baf --- /dev/null +++ b/packages/opencode/test/cli/cmd/tui/notifications.test.ts @@ -0,0 +1,267 @@ +import { describe, expect, test } from "bun:test" +import Notifications from "@/cli/cmd/tui/feature-plugins/system/notifications" +import type { Event, PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2" +import type { TuiAttentionNotifyInput } from "@opencode-ai/plugin/tui" +import { createTuiPluginApi } from "../../../fixture/tui-plugin" + +async function setup() { + const notifications: TuiAttentionNotifyInput[] = [] + const handlers = new Map void)[]>() + const session = (id: string, title: string, parentID?: string): Session => ({ + id, + title, + slug: id, + projectID: "project", + directory: "/workspace", + ...(parentID && { parentID }), + version: "0.0.0-test", + time: { created: 0, updated: 0 }, + }) + const sessions: Record = { + session: session("session", "Demo session"), + subagent: session("subagent", "Subagent session", "session"), + abort: session("abort", "Abort session"), + timeout: session("timeout", "Timeout session"), + } + + await Notifications.tui( + createTuiPluginApi({ + attention: { + async notify(input) { + notifications.push(input) + return { ok: true, notification: true, sound: true } + }, + }, + event: { + on: (type: Type, handler: (event: Extract) => void) => { + const list = handlers.get(type) ?? [] + const wrapped = handler as (event: Event) => void + list.push(wrapped) + handlers.set(type, list) + return () => { + handlers.set( + type, + (handlers.get(type) ?? []).filter((item) => item !== wrapped), + ) + } + }, + }, + state: { + session: { + get: (sessionID: string) => sessions[sessionID], + }, + }, + }), + undefined, + {} as never, + ) + + return { + notifications, + emit(event: Event) { + for (const handler of handlers.get(event.type) ?? []) handler(event) + }, + } +} + +function question(id: string, sessionID = "session"): QuestionRequest { + return { + id, + sessionID, + questions: [], + } +} + +function permission(id: string, sessionID = "session"): PermissionRequest { + return { + id, + sessionID, + permission: "edit", + patterns: [], + metadata: {}, + always: [], + } +} + +const questionNotification: TuiAttentionNotifyInput = { + title: "Demo session", + message: "Question needs input", + notification: { when: "blurred" }, + sound: { name: "question", when: "always" }, +} + +const permissionNotification: TuiAttentionNotifyInput = { + title: "Demo session", + message: "Permission needs input", + notification: { when: "blurred" }, + sound: { name: "permission", when: "always" }, +} + +describe("internal notifications TUI plugin", () => { + test("notifies for question and permission requests with blurred notifications and always-on sounds", async () => { + const harness = await setup() + + harness.emit({ id: "event-1", type: "question.asked", properties: question("question-1") }) + harness.emit({ id: "event-2", type: "permission.asked", properties: permission("permission-1") }) + + expect(harness.notifications).toEqual([questionNotification, permissionNotification]) + }) + + test("dedupes pending questions and permissions until they are resolved", async () => { + const harness = await setup() + + harness.emit({ id: "event-1", type: "question.asked", properties: question("question-1") }) + harness.emit({ id: "event-2", type: "question.asked", properties: question("question-1") }) + harness.emit({ + id: "event-3", + type: "question.replied", + properties: { sessionID: "session", requestID: "question-1", answers: [] }, + }) + harness.emit({ id: "event-4", type: "question.asked", properties: question("question-1") }) + + harness.emit({ id: "event-5", type: "permission.asked", properties: permission("permission-1") }) + harness.emit({ id: "event-6", type: "permission.asked", properties: permission("permission-1") }) + harness.emit({ + id: "event-7", + type: "permission.replied", + properties: { sessionID: "session", requestID: "permission-1", reply: "once" }, + }) + harness.emit({ id: "event-8", type: "permission.asked", properties: permission("permission-1") }) + + expect(harness.notifications).toEqual([ + questionNotification, + questionNotification, + permissionNotification, + permissionNotification, + ]) + }) + + test("notifies when an active session becomes idle and suppresses no-op idle", async () => { + const harness = await setup() + + harness.emit({ + id: "event-1", + type: "session.status", + properties: { sessionID: "session", status: { type: "idle" } }, + }) + harness.emit({ + id: "event-2", + type: "session.status", + properties: { sessionID: "session", status: { type: "busy" } }, + }) + harness.emit({ + id: "event-3", + type: "session.status", + properties: { sessionID: "session", status: { type: "idle" } }, + }) + + expect(harness.notifications).toEqual([ + { + title: "Demo session", + message: "Session done", + notification: { when: "blurred" }, + sound: { name: "done", when: "always" }, + }, + ]) + }) + + test("uses sound-only notifications and subagent_done sound for subagent sessions", async () => { + const harness = await setup() + + harness.emit({ id: "event-1", type: "question.asked", properties: question("question-1", "subagent") }) + harness.emit({ + id: "event-2", + type: "session.status", + properties: { sessionID: "subagent", status: { type: "busy" } }, + }) + harness.emit({ + id: "event-3", + type: "session.status", + properties: { sessionID: "subagent", status: { type: "idle" } }, + }) + + expect(harness.notifications).toEqual([ + { + title: "Subagent session", + message: "Question needs input", + notification: false, + sound: { name: "question", when: "always" }, + }, + { + title: "Subagent session", + message: "Session done", + notification: false, + sound: { name: "subagent_done", when: "always" }, + }, + ]) + }) + + test("notifies session errors once and suppresses the following idle done notification", async () => { + const harness = await setup() + + harness.emit({ + id: "event-1", + type: "session.status", + properties: { sessionID: "session", status: { type: "busy" } }, + }) + harness.emit({ + id: "event-2", + type: "session.error", + properties: { sessionID: "session", error: { name: "UnknownError", data: { message: "boom" } } }, + }) + harness.emit({ + id: "event-3", + type: "session.status", + properties: { sessionID: "session", status: { type: "idle" } }, + }) + + expect(harness.notifications).toEqual([ + { + title: "Demo session", + message: "Session error", + notification: { when: "blurred" }, + sound: { name: "error", when: "always" }, + }, + ]) + }) + + test("special-cases aborts and model response timeouts", async () => { + const harness = await setup() + + harness.emit({ + id: "event-1", + type: "session.status", + properties: { sessionID: "abort", status: { type: "busy" } }, + }) + harness.emit({ + id: "event-2", + type: "session.error", + properties: { sessionID: "abort", error: { name: "MessageAbortedError", data: { message: "Aborted" } } }, + }) + harness.emit({ + id: "event-3", + type: "session.status", + properties: { sessionID: "timeout", status: { type: "busy" } }, + }) + harness.emit({ + id: "event-4", + type: "session.error", + properties: { sessionID: "timeout", error: { name: "UnknownError", data: { message: "SSE read timed out" } } }, + }) + + expect(harness.notifications).toEqual([ + { + title: "Abort session", + message: "Session aborted", + notification: { when: "blurred" }, + sound: { name: "error", when: "always" }, + }, + { + title: "Timeout session", + message: "Model stopped responding", + notification: { when: "blurred" }, + sound: { name: "error", when: "always" }, + }, + ]) + }) +}) diff --git a/packages/opencode/test/cli/run/runtime.boot.test.ts b/packages/opencode/test/cli/run/runtime.boot.test.ts index e2569b0ac..8dd978553 100644 --- a/packages/opencode/test/cli/run/runtime.boot.test.ts +++ b/packages/opencode/test/cli/run/runtime.boot.test.ts @@ -1,14 +1,9 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" -import type { KeyEvent, Renderable } from "@opentui/core" -import type { Binding } from "@opentui/keymap" -import { createBindingLookup } from "@opentui/keymap/extras" import { OpencodeClient, type Provider } from "@opencode-ai/sdk/v2" import { TuiConfig, type Resolved } from "@/cli/cmd/tui/config/tui" import { formatBindings } from "@/cli/cmd/run/keymap.shared" -import { TuiKeybind } from "@/cli/cmd/tui/config/keybind" import { resolveDiffStyle, resolveFooterKeybinds, resolveModelInfo } from "@/cli/cmd/run/runtime.boot" - -type RunBinding = Binding +import { createTuiResolvedConfig } from "../../fixture/tui-runtime" function model(id: string, providerID: string, context: number, variants?: Record>) { return { @@ -61,45 +56,37 @@ function model(id: string, providerID: string, context: number, variants?: Recor } } -function bindings(...keys: string[]) { - return keys.map((key) => ({ key })) -} - function config(input?: { leader?: string leaderTimeout?: number diff_style?: "auto" | "stacked" bindings?: Partial<{ - commandList: RunBinding[] - variantCycle: RunBinding[] - interrupt: RunBinding[] - historyPrevious: RunBinding[] - historyNext: RunBinding[] - inputClear: RunBinding[] - inputSubmit: RunBinding[] - inputNewline: RunBinding[] + commandList: string[] + variantCycle: string[] + interrupt: string[] + historyPrevious: string[] + historyNext: string[] + inputClear: string[] + inputSubmit: string[] + inputNewline: string[] }> }): Resolved { const bind = input?.bindings - const keybinds = TuiKeybind.Keybinds.parse({ - ...(input?.leader && { leader: input.leader }), - ...(bind?.commandList && { command_list: bind.commandList }), - ...(bind?.variantCycle && { variant_cycle: bind.variantCycle }), - ...(bind?.interrupt && { session_interrupt: bind.interrupt }), - ...(bind?.historyPrevious && { history_previous: bind.historyPrevious }), - ...(bind?.historyNext && { history_next: bind.historyNext }), - ...(bind?.inputClear && { input_clear: bind.inputClear }), - ...(bind?.inputSubmit && { input_submit: bind.inputSubmit }), - ...(bind?.inputNewline && { input_newline: bind.inputNewline }), - }) - return { + return createTuiResolvedConfig({ diff_style: input?.diff_style, - keybinds: createBindingLookup(TuiKeybind.toBindingConfig(keybinds), { - commandMap: TuiKeybind.CommandMap, - bindingDefaults: TuiKeybind.bindingDefaults(), - }), - leader_timeout: input?.leaderTimeout ?? 2000, - } + leader_timeout: input?.leaderTimeout, + keybinds: { + ...(input?.leader && { leader: input.leader }), + ...(bind?.commandList && { command_list: bind.commandList }), + ...(bind?.variantCycle && { variant_cycle: bind.variantCycle }), + ...(bind?.interrupt && { session_interrupt: bind.interrupt }), + ...(bind?.historyPrevious && { history_previous: bind.historyPrevious }), + ...(bind?.historyNext && { history_next: bind.historyNext }), + ...(bind?.inputClear && { input_clear: bind.inputClear }), + ...(bind?.inputSubmit && { input_submit: bind.inputSubmit }), + ...(bind?.inputNewline && { input_newline: bind.inputNewline }), + }, + }) } describe("run runtime boot", () => { @@ -112,14 +99,14 @@ describe("run runtime boot", () => { config({ leader: "ctrl+g", bindings: { - commandList: bindings("ctrl+p"), - variantCycle: bindings("ctrl+t", "alt+t"), - interrupt: bindings("ctrl+c"), - historyPrevious: bindings("k"), - historyNext: bindings("j"), - inputClear: bindings("ctrl+l"), - inputSubmit: bindings("ctrl+s"), - inputNewline: bindings("alt+return"), + commandList: ["ctrl+p"], + variantCycle: ["ctrl+t", "alt+t"], + interrupt: ["ctrl+c"], + historyPrevious: ["k"], + historyNext: ["j"], + inputClear: ["ctrl+l"], + inputSubmit: ["ctrl+s"], + inputNewline: ["alt+return"], }, }), ) diff --git a/packages/opencode/test/cli/tui/plugin-loader.test.ts b/packages/opencode/test/cli/tui/plugin-loader.test.ts index 493520fc0..ce62550e1 100644 --- a/packages/opencode/test/cli/tui/plugin-loader.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader.test.ts @@ -3,6 +3,7 @@ import fs from "fs/promises" import path from "path" import { pathToFileURL } from "url" import { createTestKeymap } from "@opentui/keymap/testing" +import type { TuiAttentionSoundPack } from "@opencode-ai/plugin/tui" import { tmpdir } from "../../fixture/fixture" import { createTuiPluginApi } from "../../fixture/tui-plugin" import { createTuiResolvedConfig, mockTuiRuntime } from "../../fixture/tui-runtime" @@ -854,6 +855,85 @@ test("plugin keymap proxy preserves real keymap receiver", async () => { } }) +test("auto-disposes plugin attention sound packs and resolves sound paths", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const file = path.join(dir, "attention-soundpack-plugin.ts") + const spec = pathToFileURL(file).href + const absolute = path.join(dir, "sounds", "default.mp3") + const url = pathToFileURL(path.join(dir, "sounds", "error.mp3")).href + + await Bun.write( + file, + `export default { + id: "demo.attention.soundpack", + tui: async (api) => { + api.attention.soundboard.registerPack({ + id: "demo.pack", + sounds: { + default: ${JSON.stringify(absolute)}, + question: "sounds/question.mp3", + done: " sounds/done.mp3 ", + subagent_done: "sounds/subagent-done.mp3", + error: ${JSON.stringify(url)}, + nope: "sounds/nope.mp3", + permission: "", + }, + }) + }, +} +`, + ) + + return { spec } + }, + }) + + const packs: TuiAttentionSoundPack[] = [] + let dropped = 0 + const attention = { + soundboard: { + registerPack(pack: TuiAttentionSoundPack) { + packs.push(pack) + return () => { + dropped += 1 + } + }, + }, + } + const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() + const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) + + try { + await TuiPluginRuntime.init({ + api: createTuiPluginApi({ attention }), + config: createTuiResolvedConfig({ + plugin: [tmp.extra.spec], + plugin_origins: [{ spec: tmp.extra.spec, scope: "local", source: path.join(tmp.path, "tui.json") }], + }), + }) + + expect(packs).toEqual([ + { + id: "demo.pack", + sounds: { + default: path.join(tmp.path, "sounds", "default.mp3"), + question: path.join(tmp.path, "sounds", "question.mp3"), + done: path.join(tmp.path, "sounds", "done.mp3"), + subagent_done: path.join(tmp.path, "sounds", "subagent-done.mp3"), + error: path.join(tmp.path, "sounds", "error.mp3"), + }, + }, + ]) + expect(dropped).toBe(0) + } finally { + await TuiPluginRuntime.dispose() + expect(dropped).toBe(1) + cwd.mockRestore() + wait.mockRestore() + } +}) + test("auto-disposes plugin keymap transformers", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/opencode/test/config/tui.test.ts b/packages/opencode/test/config/tui.test.ts index db0456857..5c4f09185 100644 --- a/packages/opencode/test/config/tui.test.ts +++ b/packages/opencode/test/config/tui.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, expect, test } from "bun:test" import path from "path" import fs from "fs/promises" +import { pathToFileURL } from "url" import { provideTestInstance, tmpdir } from "../fixture/fixture" import { InstanceRuntime } from "@/project/instance-runtime" import { TuiConfig } from "../../src/cli/cmd/tui/config/tui" @@ -142,6 +143,59 @@ test("loads tui config with the same precedence order as server config paths", a expect(config.diff_style).toBe("stacked") }) +test("resolves attention config defaults and overrides", async () => { + await using defaults = await tmpdir() + expect((await getTuiConfig(defaults.path)).attention).toEqual({ + enabled: false, + notifications: true, + sound: true, + volume: 0.4, + sound_pack: "opencode.default", + sounds: {}, + }) + + await using overridden = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "tui.json"), + JSON.stringify( + { + attention: { + enabled: false, + notifications: false, + sound: false, + volume: 0.7, + sound_pack: "acme.soft", + sounds: { + default: path.join(dir, "default.mp3"), + question: pathToFileURL(path.join(dir, "question.mp3")).href, + error: "./error.mp3", + subagent_done: "./subagent-done.mp3", + }, + }, + }, + null, + 2, + ), + ) + }, + }) + + expect((await getTuiConfig(overridden.path)).attention).toEqual({ + enabled: false, + notifications: false, + sound: false, + volume: 0.7, + sound_pack: "acme.soft", + sounds: { + default: path.join(overridden.path, "default.mp3"), + question: path.join(overridden.path, "question.mp3"), + error: path.join(overridden.path, "error.mp3"), + subagent_done: path.join(overridden.path, "subagent-done.mp3"), + }, + }) +}) + test("migrates tui-specific keys from opencode.json when tui.json does not exist", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts index 3d894bd0a..f95025494 100644 --- a/packages/opencode/test/fixture/tui-plugin.ts +++ b/packages/opencode/test/fixture/tui-plugin.ts @@ -12,6 +12,10 @@ type Count = { command_drop: number } +type AttentionOpts = Partial> & { + soundboard?: Partial +} + function themeCurrent(): HostPluginApi["theme"]["current"] { const a = RGBA.fromInts(0, 120, 240) const b = RGBA.fromInts(120, 120, 120) @@ -83,6 +87,8 @@ function themeCurrent(): HostPluginApi["theme"]["current"] { type Opts = { client?: HostPluginApi["client"] | (() => HostPluginApi["client"]) renderer?: HostPluginApi["renderer"] + attention?: AttentionOpts + event?: HostPluginApi["event"] count?: Count keymap?: HostPluginApi["keymap"] tuiConfig?: Partial @@ -183,6 +189,17 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { return opts.app?.version ?? "0.0.0-test" }, }, + attention: { + async notify(input) { + return opts.attention?.notify?.(input) ?? { ok: false, notification: false, sound: false } + }, + soundboard: { + registerPack: (pack) => opts.attention?.soundboard?.registerPack?.(pack) ?? (() => {}), + activate: (id, options) => opts.attention?.soundboard?.activate?.(id, options) ?? false, + current: () => opts.attention?.soundboard?.current?.() ?? "opencode.default", + list: () => opts.attention?.soundboard?.list?.() ?? [], + }, + }, keys: { formatSequence: () => "", formatBindings: () => undefined, @@ -190,7 +207,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { get client() { return client() }, - event: { + event: opts.event ?? { on: () => { if (count) count.event_add += 1 return () => { diff --git a/packages/opencode/test/fixture/tui-runtime.ts b/packages/opencode/test/fixture/tui-runtime.ts index 64537b6c5..75fc2fdc4 100644 --- a/packages/opencode/test/fixture/tui-runtime.ts +++ b/packages/opencode/test/fixture/tui-runtime.ts @@ -5,7 +5,8 @@ import { TuiConfig } from "../../src/cli/cmd/tui/config/tui" import { TuiKeybind } from "../../src/cli/cmd/tui/config/keybind" type PluginSpec = string | [string, Record] -type ResolvedInput = Omit & { +type ResolvedInput = Omit & { + attention?: Partial keybinds?: Partial leader_timeout?: number } @@ -22,6 +23,15 @@ export function createTuiResolvedConfig(input: ResolvedInput = {}): TuiConfig.Re const keybinds = TuiKeybind.Keybinds.parse(input.keybinds ?? {}) return { ...input, + attention: { + enabled: false, + notifications: true, + sound: true, + volume: 0.4, + sound_pack: "opencode.default", + sounds: {}, + ...input.attention, + }, keybinds: createTuiResolvedKeybinds(keybinds), leader_timeout: input.leader_timeout ?? 2000, } diff --git a/packages/plugin/package.json b/packages/plugin/package.json index a3ce97368..baff4cddb 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -22,9 +22,9 @@ "zod": "catalog:" }, "peerDependencies": { - "@opentui/core": ">=0.2.6", - "@opentui/keymap": ">=0.2.6", - "@opentui/solid": ">=0.2.6" + "@opentui/core": ">=0.2.8", + "@opentui/keymap": ">=0.2.8", + "@opentui/solid": ">=0.2.8" }, "peerDependenciesMeta": { "@opentui/core": { diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index d4c2261b2..354e44abc 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -226,6 +226,76 @@ export type TuiToast = { duration?: number } +export type TuiAttentionWhen = "always" | "focused" | "blurred" + +export const TuiAttentionSoundNames = ["default", "question", "permission", "error", "done", "subagent_done"] as const +export type TuiAttentionSoundName = (typeof TuiAttentionSoundNames)[number] + +export type TuiAttentionSound = + | boolean + | { + name?: TuiAttentionSoundName + volume?: number + when?: TuiAttentionWhen + } + +export type TuiAttentionNotification = + | boolean + | { + when?: TuiAttentionWhen + } + +export type TuiAttentionSoundPack = { + id: string + name?: string + sounds: Partial> +} + +export type TuiAttentionSoundPackInfo = { + id: string + name?: string + active: boolean + builtin: boolean +} + +export type TuiAttentionSoundboardActivateOptions = { + persist?: boolean +} + +export type TuiAttentionSoundboard = { + registerPack(pack: TuiAttentionSoundPack): () => void + activate(id: string, options?: TuiAttentionSoundboardActivateOptions): boolean + current(): string + list(): ReadonlyArray +} + +export type TuiAttentionNotifyInput = { + title?: string + message: string + notification?: TuiAttentionNotification + sound?: TuiAttentionSound +} + +export type TuiAttentionNotifySkipReason = + | "attention_disabled" + | "empty_message" + | "blurred" + | "focused" + | "focus_unknown" + | "renderer_destroyed" + +export type TuiAttentionNotifyResult = { + ok: boolean + notification: boolean + sound: boolean + skipped?: TuiAttentionNotifySkipReason +} + +export type TuiAttention = { + notify(input: TuiAttentionNotifyInput): Promise + soundboard: TuiAttentionSoundboard +} + export type TuiThemeCurrent = { readonly primary: RGBA readonly secondary: RGBA @@ -333,9 +403,19 @@ type TuiBindingLookupView = { omit: (name: string, commands: readonly string[]) => Binding[] } +type TuiAttentionConfigView = { + enabled: boolean + notifications: boolean + sound: boolean + volume: number + sound_pack: string + sounds: Partial> +} + type TuiConfigView = Pick & NonNullable & { leader_timeout: number + attention: TuiAttentionConfigView plugin_enabled?: Record keybinds: TuiBindingLookupView } @@ -499,6 +579,7 @@ export type TuiWorkspace = { export type TuiPluginApi = { app: TuiApp + attention: TuiAttention /** * Legacy `api.command` API kept so v1 plugins can initialize. Remove in v2. * diff --git a/packages/ui/src/assets/audio/alert-01.mp3 b/packages/ui/src/assets/audio/alert-01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..45c3f6afddf3389d845009c78cd29fdb49a7afd5 GIT binary patch literal 11436 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 KJ>!4s`}{Au`!56l literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/alert-02.mp3 b/packages/ui/src/assets/audio/alert-02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2aa5cda6acc966be98c16dbf6a88f1f731567b12 GIT binary patch literal 8667 zcmeHL2T+vTlKx=^9B_ak=Nv>p95N#aLl|<-S&70RLqt^4X$?R&fRYRj(r|I>Z?)agE7pV;ckVo=~GaTysJVopnP z0Dx%O`Z-C83X2JgAd!Dl{`Lcp(VhQA{Eanp!CgD$C{Dlg0Ki!d(9p26i;9ZM$!Th8 zT39$bI^uACei0E7_wHq8W)>G$R8&+~*VfiHG&DE2wY7D0JbU)6x3{maZ*XvUcyx61 z_3NprshOF#Z{IE~EH5vwt!-|8{P=Na=gXHb`}>E7hu^=0;7`k^KH8u9C?fJx_?>tN z(Z8{r)t2Vr42LSk zBVa|vXG#73*)h0si1!=|ErCb;D`gs3C22;7X zL_i-3gB#e$ZEHLvqG3Ro%q%H+X7{wAxn-3QG!xwH$z$@hFF22lJ)x12FW)GYrF;Nh znp-4`pqTK2nqPHksvq)vxp6D&j)d8RQHobH!*h@7Df=#G`R&!nr?!>(aFgV_E(c%ehMd>)7Lx?D@P7? z-vn>upVe<9Rw)+L|LWfmY>k`i%Yc{oZ*A$Y3<;UIf8v4~Dv(H)a5vdX8d9@xBlX6a zNkkfzp5GNo@(A_t^yBbUVj_qnVw@liDebW^WK#afChBVD<;V~k8I9iEX%4R-XVWr= zYpsvyD@!6n2nnsDjuxb@z5tRqqlaKdT!N5EzX&oqXpTqm0#!J%g~!`N%brAA0 z-26lAaPS!4^9_Giyq!8K1J=D<&$ZuxC(y8Mq1_Ag&4~`W^r3ra@kK-job7H=+%5XL zbJ+z^j)_x22GffPVpb1}v!W(=SOp!^T2pCI3asFFd-gY;YYEm_-w(?^AH@f$8nAbi zVq(WOr>cW*KFpjSgv&^SGv9qp$X>G>qqN%dYpGvf_>Bx3;y#jaG!C~7S0_Y|h z79EFOJDD5ciho`B;&WVN^Nq*P^5WcNNAJUVb4MrCHQ)*9eX*f5T7f>uzba3Xw zuN#O3z~jd~#{joy3g)n#3cC1Y-Bl_9fa*JJ5H~SxsUJFF+e2i}HW=loj8POjnOHRk zZ~}UU;Q7q~*YWKP(Q5{*bshb4Zp`LBtrvT%MVjW1V*`VwJ(CAeAQ)MD5)4YU|9vik zYuCdZ;la}h!0CEN2$jGH5TS0xiQvgZ*+%Cx zGWI93r6V6RH1}x?rNeN0!AKoGmStY1J8GQQ(T1{lq|qORLb*bh6wfH4Jz=&UBm^qq z7$|uZl=o2~`Bn+Pp16LjL6Ihl`(mn5Z~P@lUTL{j>ZBoG&v-%O9zD?)-85|G@*tsu zF0*n(&)bH*joQhi{c*WZhGd41@+sx#fkQnv9PbGmRVlR0w^s5&nLHa9xp1IYcic+4KTy$;|25VON+K za?9OdOdwICK4cM_JL&q>h4BWjoKPQ`?F0466S_P6<1<-(;!Fs`p@j}BZN4R$k*M9c?|e2J24pX5R{FJI@6Y~1m@Ks zBYo*iop^}8i@-gyAr;DzBzh9&&=m#3A*45{ng_xNIg+1C*OpzDC^Y|VQ8|pLrZ{_# zBL!uuqo>$dkG`71;=0vgRJs-7rg*tbMeA9vztcyD4FxJySsf*Pq0;F6omNO&)5#&qV`0`IEzMfbz!MVma23)`NREy8Y* zb>vIBvv;MugY3ns8&O8(6~Tg8C&n*?-PwPv2%H3ePHhzLfDjGmkU_J>b1yu`{2*SB zcp;X=V8Dg4ItVVr-)EW^ONjI+6R@W=64=S`vSUicG1}EI$_1qZnwe-iCg?ea{d*a6 zb_|^o3`v6NV}`LYFKVJMd6nN*f1^AF9&SQa<=OM?&F|) zYc0MmOX8mh4;@E3;>r)Sv)^M_?8qtuXQ?e86<5oFox*c9t3IV!&9otPD9EwA;`sFJ z{<1-kkuQ(la;>>d6@9LHP+?MAYgy2ItJ_=@vXb05R@w0oSGi$h)qp6jwoCeXRXPIr zTohzl<^f2rXi1X<9vC*-2`tOx1hfpt)tJ6ZWrGyp_Chsy)cVT$#BZN57@TT)Qonbw zb-UE@ZZHV`h+REP>j!9HwV4PQ93xYile`sgoNG=`(Pr0+9qTIbRv8^Y(c9F>rp`(*z~IBE-Sm|#MVIK#hTi>q0i1fMNh?N+j~sTG1xcv+--G#Ya(Luh#{$?@+k;TV*SdY zQlg?t{Iv=`R7!w3Lv-bkf0e#$|Nb%OGMVw7sRWN(;#Sl1j}=v@%Fw=`j)@dPSS$BC z<~D%IxXb61ANjwa39eWLNuLS)iM` zXGCzd&5j+tQFa6tlvhyn60eKxt};A{%G$_y@P=u`DAEL@HiXgaw1r-Q$H5Ynx)UOW zJQCbJ1=P$1V-jV@AM7U3t(I5UTnp*UoFwnqq<{!tVa9B<)-N4(6xErfSyLkt-F-dx zjx4rOA48j?XDU=a+;SOAjAxuT-9T)TSJ1S$A&b9oOK=(%_a)T5l=Kn>4?&4%kw-u#zs&nHxP7_&S(`I$UiWg`-$ZWblmt4L2HgDa(h^_!y01O=XzT-8+_i89D@oy{`ZDcNL)A|8{K8!hU6!P}$Y8Qlel z42OY2$~I0-DvH6aDQnSe zEiatEA$ECaemw_V2Vd5#8ai9EFIGOTcr!X~-+3jFRH$6E&Wl^aYMK5=)8xqFqnoi^a<~xicDFvjS(0la1uTV~?J7 zJSAnA`BtH4%pnqcJ0;sgtF7#M>nd$N_O|2w7}0BKd?pzgM`N!$Mkhbjxe?V3`qCta zbWf_xY#o?{RK^*7kZqK9HP1XS=F|_o9AEidth`=vvzum zEZ<1zYG`%d%4)ct1SL9okX+aX!qnD9$3b;Zky^!SN1>pLzSoItMrwV))v=hUM>iHP zjqVaJCm$?H_vA0Yc1v%`3(OSvX_b$2p5U$rH@5Fh7Wnw1yc+e^b~CG zTL;g53p;ee8PkS6Y(D;QnY$p}FLSUHtUoVfd8?p?-%Cb<#qZq{ z@JYQ#pn&&A-iC6hI!VrLtLb$))`T}JGkc?PLeuxu3#F|yievRv>_-QK!LL}0Vet5B z&jEm#h%(#~Ay?!Q76_^VfUlMySRM=>$Otznq=B2@4|>6C11bA{C0!g!wDvbpekN{? zwW{2c{rZ6twkq0T>|O5Kqy%mZh>SseEF%NKf^nFHk^xt4X|xYuGeQe-DI$GvW*ESX zi$|i%C`YdN&!>AnDA<2bCz~vhGesGk)_$8)WLt$UzZk1GnVWVqyNMr5^KFptK_PO) zs${Ai0g;J)IX+|_lzA*+>6S8;Xds0t+^TPp=@*P{^P!ZMV=b;`kM8gpwr>D5A1C#^ zzmfP3x4W5PQ+-(m(75;}NMMbv(ycFK{#sQwG%;wA{@NAkOR+ejLBI25z1P*&e{R}q z0Nd_;-NT9m$eJiWD3$=6zkUTAF4DSaA8{M}a`C%&X++y8CNm)Tpe4S{qUw~v(&D)b z&Be9i)YXlosn30*5#8%9-J}j7?G(Qtecuc_PoaFgnu8xesg_u&tdQ^Q*|6mYT;G^n zHdxih#OcUZW>6`vA(WpWBl}r6D|yH!OlAABH91j}J`}~*3k?cCSmr+clqNZpU=VNP zV6CE7>537fnaEF4=eg5TmT1VW{F#~6?F@L}){t$6yXn*s`=)fEh+9sDu&|8(0+Yw) zePR9~LJeJNAch$}RnOw1L>lg>F7}cymG*(4R_ag>MGdbe&i6Zdf+qnDYkmmL`Gnbo%Uv&yJ62j<62As^xq;wB$>@slk}s-kx9WXVqBk1fV{Fwo!!koVcEdJ74Q7F_ zN|5p8{J2LJEJ@b>LK78v9|#vNRW0`T zr+WvyFvN-Rcd;887)QwWaS9&PZr%`C5uSO#G0=U!E@-tr(jG2aYDGqmx8xp`xY%y6 z#k8lH+4|)96_~qajMg|N7ly5zP24nx^GU^D^!&8*sKKQ7qv17BJ$&y(c5-|6Q#tmX ztKNL;t(+(6YfdXlrbbkPV1)oc95YSHjZyZMbV~8Nuen^Aj|P&RnGs!L;w0Z8iDJa@ z42vqO<~Qr`^o3U$IT%Pi5U|la@h;%|g#f+#F!JQ51RLJt)V8sPIb+1s#&~DrN@K>@ z*oN@_0B2*;j0+CQ4w|1Yl~wjPqvHINNlYHX3??&asyCM4w})CJc>Sz?o_2e9L`;an z*9bf$yX;YiR1)CO5pQ@Ii!Gt|+O~&7rYllHADg;)sTS?7xEee3J5j34aF@YES)!~D zCLvMYm59Qd3(=zTSJH6iY1-p&JAy6WeQ7FNXjN6@NJy6s5W4O%FteNStxOBty#XHU zMQgXcy`}x|v)#J$X~L}p7G}nUjaDLxqvSWzFYm^ql4XX7Db8a?43!#)X%Ym`vC(4) zg}k!-^g(JOXOCgxGzFsfN2QDzJme!KxTkJQ+*gp@7qD>Sd9%S$n}W}gG6S9COR2a4 z|L?65t&&Ib-qW8yAp-SxqkCE`&o`nsWK3+P`%7h$LVJa8wOR-YrI;m|fW_??8H+Uu zZvjnDEbE)?V9;{XVk#K4_~G3#`)+1RBH+ZLc`?Rhd*ErlTCncv`FpL$<`d5r^)J5aD>rVahNeGZk(IG7j3%pRa&TJhL^e$YBKl zVJqR262rq*Rq|qdBok+F(tV=W?@$#w4^ga{7+(~wL#!*!uoC98?egPvC}~Pcom0$J zoCItdnwI-%CHW9?Nh+#(9Y?PVdTaBE#O1-g#5Gu~6u24E0Up0A(o)dI{4I%T{7=xo zq?13||M%|lbawW<0}%5h7Z1}B00eIrh6kX(58&V1^*{9zJVx|PLViyG1Au(zX#)87 zmj7J&2Q>b>9dZ2svYo=y^~M1JzwG?OApUuLe&O~H0sFh+7jA!r(4Xsn;r8da{8jBQ r-2MuoKiB`l?ay)ftJ?o>xcyOWO1}rKv~}e)7>?E=nrF6Pfb_#uj;*f&b{y5^FQa+ zz3&n&^R>D>a0HF5ND1Spej6Oyei~TYvx`8VM?ti!<(>*9OKxjz|FX5|5 zGJqH1CL%#ZwuovG4@B@p^!~zth?gR!M7$I6NdzcjBeGDVk5Hqo?uPR#Xc$*yBFV*N zQSdJ|xKhXi#JB)tUFa&I=_)(v0PIZSL&~b-X*2nXoD;McWDRx_bmL)!6I)6@4%U4C za86_@6=9q*Nf(T!6!DD)_72;hsv7^-vKee22(G_dGdTt>>!r6k#)IIz%bWD~GCk%` z-&R-Mwcf+RnZJW424rARlB3>|IuMWHizycRTM}kcsBLRT$tFRr-oDVPu66>?JSnb( z=I2z#En{>C74};VIjef5PR{E+>gTI-pKRTmHMMf2?DII`X{Pd_UEd$FotEa@Qx_~& z@Y$5!{neJrojIqX5t(kY0Uq<$O#}|%cy!9Cl82SUPjdZCFQkUDKesG>t7vlEA|UE| zH~#o)%hJOTyhHHuab^7N`D&$j$KW&1O~HA!oCo4Q?zJuH-@SlSWWL)zFbbmK%=kk*nCjLv%bF_~yzAEZl2$M$!P z>*Sr)!Eq?kD)Ui2PN8}liHZff%E&gldW%lBX~y7T%s+W5X(jgZ7%^~#&Rd|1pYv30 z{6L2px0ST$(*Y)wF(n2hK}xut^7rvuj?@_)jGNl8k`u%=D^kEFDT+gqfIufz$jM57 z*5Yx2>%L|QpZ7+`_T<;p$|c)+?O?UuM4+t4`D!lzXt~nlxYi=ChmWQb~8u?IB%P+pKa(tYIASo`Q;&`*r(cTd6vIt>b! ziuE)?*Ri4{UfeUGkZd6h#G+cb-X$zWTB_A_v9t*h=_jk6|cvsWVrJ`C9*K za;?gl!%k_5T&BR3#|gz`#)A3%t4M*)chCbUpd(vsj@!an4t zf*vzANS;U(&o|U&cjOm`-L8X1$`=s#XpHwF$_gups9MchM~!3nqH-ONnYn^D^sCKKDc~j110gEW6LZeyj4MOOIrr zGF7FBCK8~iqM;zy8fYS8XF5^pKbs_>&L3GHdh74A{Z2Vt%04e93YA_=pvJ(>o7pFx zy7BGy(GI^ToJ-ZpogTg4kwI3MYJI!mS7S9^AM_6Tbz*4Jo%?$iLIxPP(?rAjuc&$V-~oHGcw z^1O_@q_*&LmeN@Tn^YRpN2?Br*2s4TD;k`x(rmN-mhryID@{EX!eBX7Wu@G_^DxSt z?JdS+H6_JAY5=xj?Q9-v0}w6?CpKDWvjd<~WKK?>>w**H>k%DwnuD(_3{i02MY}ZZ zh9b6JVnuIQO+u$}4(nGGg%Es+QIjA$edD`X_fJE9!rF3&3kVz_# zIJA>@Sr3@r@)$}Jh_U}Rz9K{(9kAD z+qLZyGpo4X<*pu>qcHreXGHF7td=9ElFxb$3fjEKCgAqMPTAEFtr@3CbZX#pa6Rx` zV<1B!A9aNR&OtOZOlpTXL>wcakRVKwG*)=CGSF{4#8E6$b=56=criLyFr{$<=(C{3 zOX4gcPNiH!(wo;Q6{i__O86L+9b%2xop_(zE{m-%s{8qa2|rlKJ*Y0ck#>6E#QKvS zrX&&%Rihf~^ve!x literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/alert-04.mp3 b/packages/ui/src/assets/audio/alert-04.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f4ed65209270bb1abf046093f8ac953def16d5f3 GIT binary patch literal 7369 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/alert-06.mp3 b/packages/ui/src/assets/audio/alert-06.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a0ced67c229ca7cc766b9ed3a3ba389b0ab5e295 GIT binary patch literal 7516 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/alert-07.mp3 b/packages/ui/src/assets/audio/alert-07.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..312a419c5733ed39c7ace0d5e08798140e043209 GIT binary patch literal 6741 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/bip-bop-01.mp3 b/packages/ui/src/assets/audio/bip-bop-01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f518059c54c1c9ca4f14ae0d4149fedc61d7d015 GIT binary patch literal 4574 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/bip-bop-02.mp3 b/packages/ui/src/assets/audio/bip-bop-02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b25f80ada6f8f354be124386cd83e9562cc07871 GIT binary patch literal 5697 zcmeI03pCX0yT{+TFkvtlwtMdPYI46uMAJmcrBIkb6mpA5wf)>_k4roELs1ARmugE= zgAp;J+)^shOhTgCN-n?iQ>*hod+oFK`mfVI|FzCq|Ib?UzRx?qd7tlld4A8c*6i4( zg9U3SI5|36BfS6s7+cygUw!S3IvY2WNM9d*Yv3gd#(x`rGhGe``5~4d@{t5UOcbnN zzuo}B5y1x`03ie+JUpC^a2}xsp%LK@LThVl2LcPB4`C4D3Bn76iHV6xgtrLu2nz^H z2rvwT6832-uZ*0BV#?2d=2goRY4lUN zo3?RZSy9dQXlc4?Yk^74ZJcV?9y4N2_lDQLpA0-knP-*<3dr4?T-X%%sr|w=O^f>m zbj)`bHSd134|?{+|AgcFrN<}F8%s!EzCn5&@O*G%$O`4EN?1~;)s|KBT$4g?hts=; z9t6EQ5yiIJ?eQ)`=Wg!cQk2QrVa*4_4Uy9iTvh{qt7Zom-fh~4gCR+;oET@^!|b0L zj}PZZ23qg$v?pT)ANC#Sw%wy2=xcMeQ?}OPx5=x@`cDm&98k6r?Bim$vln$cWIbvt z>7y?XQI5ID5%9Imyf69KtS=X@E^8&_iUViG1)uPt|x~+vFccuxI&yvGq=?xbX7#lj8a|nMr}YhTWuvsia9y{B2tl^>>rN?T=`PfFg;9DZPL+Lm z_H?{_N^tk4yV)xE_QtyMyk*0j0v~_ULhq9jT2JU&B=kU1D>WMy@dV6b{iPT^2ps5^ zww2VL+uAM^;}~irN#C(j@w7m*<$>x2d*?2x>ZY zV$(D8$Xo-@yGS%957L)sYr2;Lp-4$Q8VR(4uKPlT3HR+v7rgQB2Po zYwyJa5?0N3*X!~HZw1Un^}O?bh9rJ*p=9xt#MF;UJZN!X!MLf!0~L#2>AX^{XcG8v zyLEUyaWGs|b9XdbFuF7Fa$52Y%JIqo?}NLwrVrFP%*>EhpYG4}o@*fTzaWv0VlaMu zQ(WEp5j;6gm%4vyiNnOTaifdJN=P>))Z~vJFgoCWr1RL*JO=Nt!4l6Fc3l^{m^0B0 zq0~rmW0^h$HUmK$>LT-ucdB%K4)~nFt;jOIB|Ja1ks5;W*p;F;(`$6fdi=g?RyplS zu4mH&)7@Lb7EfO0nTc_F0jpSJ8$92dWg4d|?u;FcjKfCC8G6Meeh8g=f3>WDr(OP5o9St*vWp8ZWxb z^#Nw$4%xXx1#&|MORxFFGx0QdyoAjzv+&~$B)IOEFy)HvPU7x|zsaD#DdI~o)Sx`S7fAg~Frk!5^JldK0_2VaavWbfC}Z>+w%fmt#WksErg zs-X^spVw#ajL~8nlcM!{N|a=z2f$p(98r>xlq{+{>hy4xo>3;mWZ6Ad|D$B(?a6&UTy zx%5o->ionf_vS(Gw?S=obx-$@FHdn@>#$4nCkdmAR_g*kiOL}K}uu%y4OnbXImyP9^vTNdA-sH2M-P%EQ zj}+WSyCtG%#clxW5}CBmpack9 ziO`EKX48L?x19M%B&Z2_U2OldY3iY$R%ehp)mW%wu=2pk0(rr0^TP$vfjwn`gmav( zNvH0uLp#@_c<%jD?H(^HAGy+^q&#UR2X+$P0+=dz9!lqgm>xWYLvMfd4i&4Sv6n-! zlt_ABMd@X0NLk1c_IS>3wVNxj-D+q3wTf23OAvDc5La-!XJ2(!>DN#GeodgP>Ms%N z1AxD>AfYm=FT0`>EcPYXCi?%w<6qAGrymN`KNI9hxImqZq6nZMq>j%Qho^{``Y4+# z=)xp2R(qY;mi4*X2IeryM__Y^uCDNu_VZwvBn=U{fD^3(caH`o$-^8J6`ex8!Y$e- zm?UnYq#_w58v{?x5V^#~(PzO7>VuHDq4&B@EpNUV3lUy|90@OE2qttuc(bSi5%e3Bvle8w(1Prh80@D<(vw(=2K#|cUa%4QV7DcxqU1T-d z&)Ys^9>?cFHM?-!UFkyVJmxUG%z&3j%#VO_`=I9R!C2~@RG*d7RA)EP#}Gyluvm4d z9CaAFgOUglI?zSMW%jnJrZ8!$OlCZ<;XO^>Jf<6JvdxueJ(AktHlVw=HUyv;;z|+$ z7-(HNH3{=rpqwj2GzpvclP?O0b5J*hl29QyUu^L@4n_@=gptF3oeGO(TD>_hN`0d$ zbMV6_Iu`@rv2*KK@DhPJ7TZ!PBuh7wlaq;+f0+PrVX1`EkeE0alZpdsByI+Y#08|* zffR;1AmC9oYb!x`n&6hB%X2j4y=Up&q_8dGTyorT$oX+YL=u`IDh+Jaz&t=vsbUrk z9@Lu7a6E&I%1tO#o1d!1XZS<%z zkQ7DYo!!NEnM&5J904gLerqTMn@^Mg zNAk5(2l6BK8$R!j8k{x!u*8B*Lpx+vU5_sItU5Qri$5E`Gs>Ng$@s9pcX4#Sqxm@u z%SJ{avAA|NjWN@3A(9S_SyRZzZD>v--7~ z1bHD%j{9~e20$f)+bvE68=IT=KR4i8>EuUqLv*1G+Yz()9?|^K@vL0CH#w6}`14pB z(IjaDj%2J2)nB~jmhR*dmM31|*qaZRstOBuDW~%b1X3o!gB=6%mJande!DPyL5H}Y zGlXH)D22Yx4Li6;m81OOMq$?bc;OqP(ZOL=8ZO(>@m$tQITyMZ@N#|j?F$9VQJzvM z72J7yqIh{V)1;?Aee?>d@B#{)7e`_lPGYu=vG#l{0ed2gFb47K#$Tda4!*W6BE+zY zNO<~Qlpss`3^Dy>9zWZa-^2kQtU!+?!b_RVC9wWf(e{->2>nX5a%D!+oi2o92?7*E z@lyQ{yHj>-UCCD57dMdTw45k#UjLHwc-zrsHE3hXNG3p zPF*UktKVko>^iOOpiCJs4VeDD_Hjf?)rjMGBqx9P)6gKX-^w;@pf*nI@gBDVYxI3} zAE0Qn%tY*$;E3mq zQSaLw8*<*}!TeS(jnR|#%{u?eoOK%GK1@j22M@flDrJn*?weIx+upHBq- z7Wth}W`(G>S9gCovHV(az_4OExja7pkj|OO$4?#GWNAl?(|=8%lS}FS)O5+oDWjWn zbMqd}&e|{d+d_s6W`=@1^creB?iia5RRI2S`TVP^B%oJocvh9R-?$f7m&Na3{l>S2 z5y(hOY@gn8qUiNDjS%2-0%b(@@f-j1g-i*_BvKodd}J?tjNNuo9-!(iZMJpVIRBRi=O2fGXlUl_{~KMxOZLo9 z0Piwkbakw)l9I_bd#j}TeW8D&&cDj#T0L2Wtiz?$XKBz_Ef?GDQl89r@gM8|a2jhm z&N_mpZzDDkyyq6P6YRl`uP2qKi27 zLJj+4{=d)uOC4S4|5&H=+kXD7^Zh#h)c?=-^Z)nU{;V!Z literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/bip-bop-03.mp3 b/packages/ui/src/assets/audio/bip-bop-03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..adc68c91dc29e0023efbc142aa2f06dc33c6c6ff GIT binary patch literal 4186 zcmdUycT`hb*1&I)8we0Ekbt2mp+!0g0R;!gP^A|E6$J?$6a*2mJxlMO08h@F`Lhr6@v%@YbxEHOoJ~^{ts%>-(*B&)uiov(Mi9o_p@LG9!Ti zA4L~uXYuQsYw6~pi=>x(zeY@8~{+-fH)tG zEzGxY-NJ8+vs=V$!P=sNPtz7%TMTaTW{cS^zHae-3mzZ7=FK(wY_5?=O4HZnW&R+g}&!w3DKUu!ykX<@s@e>`1fc6fH&lJ*Kozxe+-8S0dz~{xPb6CC(8!C zD8an7RIXYyuqucu00Gq*xq|mM9RUzRAtMN_;5+f?orGo!B}v^oNQi}jml+7)P=wcT zG~~1L)i+<^9Dl-bXsl38Fu@HbxP!Czn4XOsMo#Nmcu^*)#pg868e(ea^N~1DnMJ0x z3&x22XvmZ%xANBDJ3YSbAwjipgj7_?`1$ctS%-I+5m7VX){88qXVWhCdAzlE z%}8>>4kl+tc!E@>?r-YceE9;8*P5BV$MewF$JxhhPOxP%yZknXZenHSeKXI9@W=kO zyup##QO!%=&*K8J)u*$M1daj_MfG;8hV)aE8Vr_*y}Voc9A5g!9;_-PHyg4I!4N!9 zx-+%>=Z2{(`;xm5m-g0IKyVU;H8PP+ae91#ZYa?3;A&=i*i_xq$w$`;;@K&sPD_W+ zzHE4iMvI(1e71kVsH-o?L4@kC=c}Y4%-f0jV&?L}OdpKcfyvWp^4+1|=Z!t)16m`l z^~Xe6lckn;b4#~qa6zGH5)|@P%*3<|(5%U-=9DS|4*0yoEO$uBu;2$nvw- zoqPRa<&Ph%S2}OHejA-yC*E;t;**5T;)?4IDvG^H$I9+7 zJydp;4)2kE=iE7ezx?Wrl@O7EO92ZRxF#hb<>eS~B0NE5n)Ufnm{4(O0vSwMSa?kU zQ~(GHnMKA@q;1ui?n4OIE6)-H;i?!tU-P2PUbk>5Q+f3O$B=!^!BJy`3#Rn(LglJm z0}5qq<3q>Thx_))J-2GgNz)x&Uq0deY%Hg5+B!_HW=hITMp<1-8F@pVw?^TP1ESHQ z`nQWi)wp@@qsFi$TW{pYG$6u|mBOn8kkC!n_UD^@*Jkn4F*H$Gte$Eo&{FQQym@7~DxHgJ0* z=zSHnjmwmfvv$)Gb~9oSU_uG!d7o4vdeIN8|3?stN6-Z%8xKHIRqkoGbVoyqh+3t_ z>oKF{G9uv$l~2CayAqgoZ*Tcz7%e`t+P;FX(@EH`Rv=APiR@>(oSzDu1m5G%9{R)n zkwJoT_T&B?QR4v~-+D>`+{JS0MiU)?Ld&_3M0MQ_S!y?=jIII{@tyD<5cB2f&%f>T zYR&aqn!`45oXz03F)6AseNK)hMJdyy<87eDoOQo2yj4!HkcHz-&%VyZ*BaT6w$R52ONo~Hohc)9b|<>@hX`q zRXMI@#_fH!U4jZQI$|__-oM(QO%-pF&Hmu{7a@OkVp5-R>OQ^`<^bg9=bKLG_F}P0 z+s3d<^k{EmtKy74T`{V*syB1IYCc+31pU(`uxN-CO+%`E#=DgXJUXD4(jA^1=zWeRO)*P7~gmBXL$aGiHwipYVy05aV)>#tysC`RkV9MT-+y z8!?A0xV^Wk@jWCs{&uw&r9{6!vb@#xD(l9hz8|-qWqYPOQ*FnLxpb_k=SHeHZ~j%N zHyNkL(F2em1j7NV`sHB^4hFPUhWj9l!_fi>(cDdYXxuL}QSJlM>=$@ZsIp4b95G6{ z+-E;k<4!l%yMC=Sd8o=2kg>_XtDj&w)q@^lvwIV)*t=Zf8Z@L946osB%1{FnFB;cg z%~Qu07QQU5)Sr+Ix_Jm+>YHKNcctb%eX0=!-Ocb1xj#%0nK6KJP^JVrjO2(bA%www zcA*qZt80jwLOFO8#%FP^@MKupVzs|~Z}!BhfEnlN-iaR8NaWq#`uH~Y;vPAcsDFLN zGr>yF+J2V5T}$oCN!b-f*}}^B$28d>^9rp>ltt{0@br+lbITNH;_~A)>PO0Uy-XfK@CVH6w=P}Lq2&5_5zJ>$>~epq{B&|_K?tb>%5Vu|iKI)g*=zVck)kW+v%5?qluPx{Gc`;41)i z&}E1CGm+FON?~Hq&Wjli_d0CTznc5m>$T`pRxekLoNc~lml=|5c(^H6^$Tq^!mFsa z-mXP4PqHx@AeP>XT86pUB@qKLN4pJmP|HXGLD_=4T|g@zdou=7M8> zYU@gHoE`We|5NlLPAOC(9>Fno;H@JiJ1n*HZ-6oB`JlM{!b)8YFe+L`hWuVqPIRBz z`{+0bQ-xkV?3jwBsBZp4V%29Z$(X8z?{sJ`Pi)xXVJTs&6I{4hvfe-d0Rj!}Fv(H85v%b1rbLCfui(qO z420aldR=`@&-Bqt&Jy>+!R)@uC;Xs(0+2t4iStvP6_xDG0}?mQJ#o;*-q_k5)vT}8 z(We(bKqDn5G_lKZEJzqs`OL;T10@CbO}}u@Kr{}P_p>*0TXBz&=s5t0ET_)pPv&DA zN{30Q>WOl@AIpIcx(q;3g0i#(luQn%)a%h0Cw8P-Pa;c`lU-gGD1*{gb?xSu`%=i^ z){DhS1|7<5p;HdLfQeNY3WIU^U7=DVw9GXQ04y0%q4|JhjS1$7a8fa%odP+1Yq2}a zG!17ffp7^TznFUHe!c4TDUZI0JJmv>V|VnjS!lf}%ONwi)5E{i38mWr_AAL_8-Jw3 zf#UhW^18)*pjK>!r_uP8AJp$O(b*!>=GjmXv)w-`Irle6V!%;)M3FHgOIFmlLG&uz zz7{?puqQL*vqvGc-c6A*=$13XTRRr$m_^Q<33M!2`V@W)U$5`VrfNM-w>7pi-%57= zOjUkW1}D>ik;5p$>HOQ%Ck0Th?LX6*y0=U%pB!ulEQ-rS2I;8j|HU-Fn3`|T0$_)$ zCGzbF$xvxa&Tag~xPLVL-}Lrl269NfD41Iy`^@DI$oVAgBT9A_fo$MLI~A zE+8NsQ9w|7Q9+7;@Suo=A~*4w=g#~7bLY-`@4R#WxSyHbJ!f}kf8~45*)u;~O*vM8 z3D(TiREu%t000(!$7?PpWC?Nv86xrb!@o}ZJEYcs=>FBTbh~_!(K^UD_yK?y1+X)~ zGf-u6jscm0BNGn>ehfmHBrtf$pomE|gJuT3Ox`eC{iAPf&n1>+u#~{ zJ{ZX6#B{&wi1>j{(iHUGLrm=5eh!(0B_=0cJ1zMzvtq_n9z2Rg#c1{&*5T6~?>jHG z9C7W|{bB9_L8yd&Xn^7Q{hh2%2`Vd6n)gmh=xl7#>|(_Rz*(8h0c*b%ntg@%g-5(( zJxlJz(o;o=X?uwe<}6D#`A;AgX_^75uipAKh_St>yuIpRjzfb%A)=6O1=Aew3WYQ} ze~417z$bctgl?Ro99;$C7EFhS9#AyH#%RDc+R!|GoUOqecvdulQsH|xO!Jc?_DJ`o z4=WDgAQx~e+I8K?<&cB0qX0S_OvO8JKo(iDpnO0syj72A40*%OX+T5)Mip>6JLCmX z3|u)%?16GaNy0ZL+o<$wa8LmCIS5RGPbI3`Mu)UQ2QW|ojd0)s%A5-bZD^A1(~gg2 z{xp^>P?D!<>nuQ=LwmCV^qi0TW4G<>z@#HQ00<4J`E~@q( zoLpOfL?)(1E<#jNYvGgIBvaY_?>+-TV<+vWtKw^$$w{vlCT**J-{0TgmF-A-rA*u} zdKoa9E!T~n%rre{tl@0Qy zC#97v)0Mp|j{9)9QXDRmrn@e(y$&k86!$u@w_1bCb=fxwyAa@8>SQBkL`X?)Ol|gb zcV~`-IRNi>NABmJT;M7EMb!opt95bNnY5f}TG3%Wzlmg;*nIS3q5b!{ z5y=7Hq*a}g0=3YHf&Q~K52J6LV2?)%$rmWHMN9DW)QV^;N{{&_zKK$9P@cZJC~u$N zJvtQN^PZ&v~3chS27lUmCSYrnEk^&;j#Fg#}Kz#2j3I7>7LLBqZBO`(olqeJlbunzi3+xv5Bct6Sb zNBFWJ1$%D!p^gJBo%K&2a~{^%ij1!!9rGx4)HOsX}VSUTS!lqOjevDR=47f|W%zZ#NlM5TqMz=-8Rw@;sM9BM#%*g%T|A z7kh%PX|r-^*3@tD>p!pO()I`vsXFoXf|ke6)p|?vX(9lJvF=!3@64`2;Objv(`CQp z<*Mi2t1%LfJ2jabViWu1Sn_~PUZqrRV8Su4aI)wFIe%6e4`{~F&SUr`D%a1 zsB;T2z%i1rcHFIkF4j<%uVA`Xcuz6nolqt@EF)eTm$^UvT`KwNuS=3BLA@7f=mm`S zrNqx^P+MXV?;urkkuY`k{M7f!Y@Dfba7cecY-w!GNt1Fb!{KIKa_~q-iKXt+q*!C6 z<9xk~_4wlHOK&PiL%VNbGcQIa-_|>0=4X{l`YP&QoIq-XC%K?Fxgr^|wSDbmg9rn` ztYEKhYVJx3e_3v|hg1*>d3m)yT1(7orQBxgrqISj946@HwP9{FDw1OGuq3+lx2RP8 z2Mwy9KE8Cj#lQ5EA!^Rh@xWs1t;*5Ngk_bvmxblQFCIy}W(G{l)% zdQlWw(VMDhkV6zIg%ry#a!ftL^RYU%tN2E`ds)Ryve=uGK+0)t7p^=Rgdha zy}4c=46bD%WxHGU;}#DCtwzHvsK>iKX=BX4%1S0uYBt>e=2QzujLhl zE_O8vf4uHFrMwyR79qy1vUR06FEPVEs`ope^`QdhUg`%`GpX*+5I`av3275uU<2`5 zsizRB9KL9Yt7G~c>-+_$-4O#NBC}BgG4yox@joV zuei+HTg|<*zzsWmLr4%$~*_k69x!e7*xaT4z z&Z!j>xS)T#!AZQZ6rL=EPYiWyyt^DAUe_A$lv;q6ljWj*62+;HZdMcW z$dA)Psy)F@ZZae0Zc{a>nO_9|_&I#iJ+LxE_*80Lz37Mlps5OQ8f?Mg(Da23k{sGV zp%NJeyrg)VbhM34dsJVZe^$vB-@A8Or#9)P4x938rx+g}YuF225PXZrQCV4c!M!Mx9?8v4FWxFe<2;&*tPgXU{}Vv&j;XEXkud{8k5oE~^ZXpS zqb@j=7FlvxDz^B|(?eWh`~E;$mn#Xl-v3-KqSDjzZRs+RmbIF^c@)yo2t?7a##(dQ z-FyO}oh71h(maPfg`@#M#uV?!VninzG?myMRhxu#t|oB(FMvr z>hbnJg=E!Tc^rI~^*A(aJZPU5Ioo#!NUQ*uqW%P8#z#)CH50OG z^HO9CBtF4#S*&_(BzJh>^@`OCg?%Aa+4;+|LBW}|-}ll_PUk(e3#2s-uiLR*lmp}d zk_Z5seSMHkHfsTm!G`srG`-Euw^XKcA&w<2RpKs*F9*Ui4(5&)j4$j)U(ZRv;cJ5Z zYx>1n;AIyEb;Glbe`K6U8PE6`{iLsp8P8{`ekdcJroLsm&{9A(;bB;>Fxvf{=o48# zXYo1Bc`m*eg0#2TpNH0lr z75G!af)Yzgp%Dk=`$;K=NvBkuLGxIg(+iT-tmB0g?f79n_ZnfFR}ZBR{~ArU$|%^^ zHWggF4^OjTIjX7l2V-08M}Xbo3r=GJVWSsywiWbd^m!y4dHgg2)Ffqv6_T8`+|_;p|&{NJ+Y^3hHj>g14aU zvLE}*lK5QM3x1RJxUTG4fot*(Dc#0;1~yhlJE1OcU26}IyW+pj>cZaM_<)(n*ijZ& zcv`l6ufYKzaqWgVAgV6sA;eDnweld98KUCInDo@ey(W6|!c5>W#H5D@-hyx93e$BG z<&cj)Q|{B$Dl27pR$^i3mrLzHjdjuDO*hV3nKtV%K7z520l#=fRgdJA-eZzi^wkWs zeiuNB{|EeQA7RGxoyyjktFlJymq-v;Y;M9y{g+<+Yj*x!pJc|fNo7mVSNbG_TsDex zQ3w~j_qXx?+phoZ@@Z~pgz-<}n0m zCd{iR_UgTtjQhh$s>I%(b*deqXNtY_E?%NNdp6K$&HB%$ynHBj?* zakqZ#YvY8hGFp3{sl+_N4kF_hk8K5yw(qwvF>FLkV7~NZc*z*nh3Y*7>8*1bSHel5FG?-QgR}S}}ujpIis` zrO--EG^U+LQ18oDFSc+@Q=HdJEWdm#)~Q8R=<)K)<;wTv_P!&jG-zGw+(fhRo<051 zsN>MCGb2qjxxM~V8*E9{kq`csL$r{f{V&6;Zb9P z^=5uv!6<}F5##8N7WXEu+8&)|P8fYOrfQx(HOcV=*6nVHwo(ZX-)Ml_Kd!F6!9%r_ zo{?2Bp^o>qsG=whVd3`R>UX9)jhR7xc@Edxdbv8Unkv|a1R;EpS zROc(x0#Z>UuW_L5-)s{d?2VTVo33J$U3X)cLwq9xtvy9BML+dut4YCOBpTF~ z5?@V9P#8uw~-a#y%j=B^ml*fpCuOzdEs z;lQ>>`PNb6HD4_K+}@$BdF4W{GJ+OWZB_c4hF`{>PS&~Fy4g#YOa_BN=BQT!HVKl2 zaM4pXl0~v!JP#Q5^VZ!=Ec`HU#Fj@LOhMr$j3d%N8}s`JP3w#_nUJh*g6W0DkzeaZ z1P^>26y%+1Boii^dt#Dl&d$Wl{gxR3$T)fA75)HLJ4qx$DZlcHdU9&#DeJStES7>N z3r3_appOR4#xO+Gr+6r7SJu(6cfHgRs`MYyrUaWg63bOjvIXSjT>3=0xrf zOJs8O08OL`R^gVYBMwNjJ$Aps(r2TZS!n8QRWjyoIV!aAG1bK$22zciAfwNCw5=hE zFM*B2z3e@(nU+!dHOJ}g7^)n(44&V!+I1gZekiG?N8WLhKSje7sawyuXi+Cw@DRNN z6l*!glLXi}9W_i!Qm#XPl}J8UQrkp?RMsKl%Lt5$ zOLl3YqirRcnIAVe?>lML2p4(sFjecSD2+qGhRJ~+biqQt&(5T*C)kQ#%RgT$yJA%C z+j+;6ub7gIdV5Vm-eH00aI@dyy;#IyuoI zxd&DWvgbo&)l_A1AUJ4y`sg-5do`N6eZ=vRc-m%4V1!lNxH>PlA!rqDXH9Z1(ka@~ zFIR`i6`FhV+a8=q|BF_l0cXt+{SoIbW4YiYY?wijN?i#XY_)zg%9;mkSLzI$a}fy@ z{Ky)PIunfRN0HWoFv&cCJsk!aBrNk=OTUT473W@GlfW zoU%5bxLH^OpZQ^C@4}JSmz`=!xy~N?QL*)vzy>(RpX()SPfHtE*P9bvCA4f-LfNJ&t=$f`kaNFY1WsvA>ol}G9!@fCSwNTm6!2FQ z^G4s$gfch%P@EBSCsiW=;oFr#UM{##Y=qku>zlQ}+H$+>Yn5c!aIo`*`U+0uEjG)l zTc71)@BmEN|DHx{->Q9nL=`8a=+qmfYfbW0i|!z?oid)kPvs4IcrZ%>zu3l~(x8}t z&u!+Xm^=StF)91kSbm=|w&H<8i%FZ#aX+qpOfq+N$<@E>_Ybo9pSWB)dy3w$X*bdh zCvgYIHPVU*$=s7Agv7X0bP!T;VvtelxJM=1ov4&cMwEM!N}-U$ z$hagzN*9&uppr=GV#fUF{NCU5c24i}yl>BW-oMUzp1t?_eE0gU&)RE$_gc?)+n8%3 z!HUQ`ZQpLOd=&x!Ve1*TS6542TV0bv`F8V<6K|2q{BQ6N)z$By@3OXP`H%;I5Du_d z1qB6NU0qXC)2&;#IypJHxw+A3G+$refPjFQn3%M*GzNoFP*6}>T3S(2as7HtO-+4$ zeN$6YOG`^fM@M&ecVA!Mz`($>XU~R)hQ`Oor>3UfzJ2@t{riQ5g~i219&d$tIYzJL z7&SFlz<&uD&iq3qgj;C}YJbyti+C15Fad078Ik}nW&(f&p8;%Ul0{a*uh>v6q(vZ{ z{%$&iIwGHvzKvp!tnQLJIy1FPS1hnL3=NLs`qT;4qrZEmFt2(FY z$I`Y#G+m5S?Rm;u%xA5I;6ytiCRY6}OqdpSd=`9+K=C7}4-+7!hah2~{x~&1P3^fR zgfJ1sLy5SsO9|T2eIE=x;r{XX)D%VA$hEIhv50}fw1ln_+3uK~xX#p6|Mj+ZRGfsN zl%rFH^`&NO*wetLl@}Rj3NiwP0NMy9)E8M~-^X!&YX6XXV_jpw?{%UW6-@{d( zmYTTjKK`y%O;>dTsDHm{|)RCnWSr zcNG25OtIek(eT_j3zm+F#;!A^0Be?6VurL`FI6qzYAB7Or>SzG7@v(4IzRKgK?Rr} za`=#&*^^Ddz8XkztsukdnqFo!Uo;c8deoD7JWlMKaDv|p6;cSA0IWA9u)Dk8{jPsm zf}LHR^kVa(vx@RH19hic7qW`4AHi#DFHKCAga%7@a%Wyb0ALQyg3z=}n44eMQ7DQK zflt?I0A3uGou60`zEQGtsv99WxMkoS07S)Cj1lssYsyY4=8cs|K zOu6C4TTEjuLd3E;<+{cgMDhi8lnxVC=;3}&#>?A2%E5Y0%XD?)^p(pG170OtGvf?9 zKYg5Fo6dfbQBtXYb>Q)pEO&|_Ht#A~m)7%Q<_ifATh9ZJ#IG7S(Bqys#h2DG?9slj z$F>D3*#3UzMyz`DIw^u~-rz6m3#V^7`-$kw@g>+gXXkYowG&sn=OxbUu}*EtjA-J~ zD>5~re8wi{cb79O!!bw`ImWeH(^R^fBmPtqFZiUHo4>vK?z+&$uIDrWRu2L2^eo{> z)+8;6AoAvpo4?;+uHS9-GwkeOiTD(+%s1~P2izrXmCb9r_||8Mhm?uNbY9r4{V=P& zXh=tjF;#@1je7UJYxvOWWl~o}qQ74HJ!7uFN~zKA#UMlkle3_CDu}IU>$T(Hh=p0K zO?CnU+Ld3C9I=*3I*l zmKH*P?Sf2KFUd1**E%493D{r3s5BRH(aJ(cj)X_|hJ2;tqnDz0YU zlelfXMGw|n2%olS%*_FEJr^!=)7OZhBL~xO^ykEG*gK=Jw6weGU}fAE19x}(&+?-q z%_G6S^ZW|sY6V?gU2AZfZey>Ns))^y00*FDoAgiUC@e6sLhH54i~F@q!{A#G3NkS= zE^Z^@LPuq4wOpF=CwkRo@iA0yvM3hahB;n3ZOX~)e4{yAUp{ZHh1Vjh`8-tTNhL7~ zN&G-#xthKdlT}+)BmPyqO@$PW3HhbwT>q6piB?5$31Aq#lB=-6cp(GUzdBB$s@I4;5Q;WXX&J z=P0~Id)6x;_@R5#boS@94w;ji6LX~F9=a>q#K(_`p*L-<8tA; zsIpAdSsf2=LDZu1sHa{$NHvV3Cy2LJR*cPLhlsbV>Tf*3esUm5aLW3C_Q^ija{&S4 zOn_C=j9xA|v$W%_r-$7?1V||z^k^V>GAA2YA!Vx6>u|nQy_U-k)bcq9<6cRn?(0LIV92Ink7U&eci(J=;8MKheK18*K56sj*B4SMCl+5 zg|R&pY$V$V0e&8jGiV&j60sM1=Pa=f5k@oUacc1YY+==siWTavai+~5k-}%>o%+o(1T*~_n!oT zRyhLHE*4h5lE?%tPd5f(%#sVk15bJAk%+^%vBo@VvMU@ZD2;^(b6k-xb&+z*-Ig^6 zS#uIK#$Uzfe@@Ogsf- zAfVyB0rtXDFk~$;w}ZDxV)1~LeWKbl(J{Xht9VqRG8cCP;kd~BL+ng|1`^k@i>Jnf0Eb+P$;Le%C0xsB1WRBA5@VbZr%fPO4G@?M1X>e^hw zSP9MPm(daVHdIL^8_gI$akogszg0mDeLW`6ElX3!NU!I?O26j;3;#U|`G&sqYOGf^ zf9iD`2?KXc!(VyRyj>}6mK}W-1RjsOF7-?Zy)Z@6ak&9<$+@lc8>-#~)sOdep{V%) zxEwGxSk8#s_InRxRUC`%Kg}!6gZ5QEA-IzLD))N#uB;QQb#k(mf#o-0p8=}@T* zYJx`D5z1JI5K0IEbv=PtW(j6kv1%;Y(}W$b-D_nL^o8gVuE$j)ZkqN<8l^Hx`PL-J zqdqUR3yD!a=8PjYUrs@1q`^ht>iU+rwY%w8pXJ)aX#>8UE>mI%)5%zMU8_*`izLrl zhxfs}K%u3oM}xY?_3cex=8dnsq9voUj}tlg+x9CD-fZG=xYQ#!(~MK6=6O8Pi4Brf++)YMk{$zHT3lM~ zt*@~u4_r0Pot?Q2(eS8&VTtzF4QH1Z8?Ffm&7KHSp3fNVC3%}*P^V*|nlfft=4*WT}DStY|BSAm5j`jk2Rt=H&@vJM<;jps8B^45#iW- zb2GnPTeoIEjv*0r|C7VHnv&bM6PMqc*b;=yN;<`Q>zZLuIJ^Ha4UMTFQN2z?2c6}x zJHx}m&SEgidv)JyEL7iq7VH1fEVP$DW#1GZeXOZx@pECvN8w-C6Q5SoGNG&cR?QzF zvmVGYuYVC1ZOqNd#=}Sg?;wzDz3Bx@KHcZCt2h5mGHXY|(CV*Yd`n?xwo(-7hbMQc zpE*fIRt}%}`dR2|VRvm|{@V6qNn)yVa(BdjHNj`AaLOs?eWc)b4wfet0|jtv7!c;W zbA4Uh6ku@d!N>X|xg8`@8SaHJ*h)u9Xz&&TScA|i@WPmT5n7u=RNkDYcEAojqBvv} z;ki(o!J0Bs3_kzBN%lCGETn4H!}HTF*;AubX3+o0a0?0Gq2RWjc&1Qs-QhwLC)CRn151!&wz{#zanfQy{XT1wIA(jEibqyO(}nk({+fv;md&P}8(cDl9Z?MC zrJawG>~GwEtMjznF8-RLB_s#)(*F#AbRvFl5Ow7ATC8yS;u}Q|by3vF9gMfz8Rv(- zbz-N_-;Tx%Dg~mx%JhZL=BW+iUK{mD>8(`KB_KF^OJ|ziEw_cRH!?wd+}ScyNgl8E zBqP+*-38(1lR;I{_O~!Wz3#{@AQ;B$mY!FXNUnIv0RUETPdeOVR`cN19qwxeCoDS| zR?@R|>!W+!j8s2dwA+xdUqAednEZ&=o^T-PWjkCon$BRJ+h;wMm!v#l#pe}rt};M{EH)hWcY)z{|nC#rvH0rC4vRW(tgL<;SmLT9cFyF_-^>l z{O{rUpIxqWAP!_vu~jjDIfB!P;~`kG&UfZN4ZaI~g=ZFOnbj77JfrE(aieKi!=Lv2 z{lvfUJc(tdisJKuhsVEn(BIqtEcy%20OC(O^`G_p{k$LZ6fgG)5&#>Zl*wA#)0EXo1 z;$k7N;sBuRy`uf}wKa4!C|X*}AHTl%^CrCiYJPP+0z>@;o`m3$5g0Wf4Wj{L1G96P z5SW875X`Yxa!Kd4fDG3Rf?^w6R8FcOtQ#5XJQHWEDo6p{TvQ1pJ|7{}K5Ac%WzQ<) z^TLDU;#k-s1c@ppbagPSD=updux)5XQ6)P3S{kGDu_tB26UG1p7$6Gy#yXAZB=M$O zJ=J4`VjY8<^XVEQ^1B@D3zQ}j3an%aZ2sg@c9F7s6`#MfI6ebUsjlONrmA28kQl@0 z`6OOMu4{E319KH)!bH}OVCubyE$V7nMcA|r&DV*E+gG)v-5TK$_BY-=f=FYapU2C& zzO+pzS8%r7mD?@Xo9%nmxwC8WZI#6|bBbA7`Rw-ToA(J`g}EbTKdoamhq4CbO1B#s zRUEDB+En!ZntR~0&o%8k2Ylzs)nngQYz)YuSB!KewaPcc*+Mfz#%)7Z6sQNI8(=5lA9nhJ}NikYu(LNnfAR>GIcqf8>ykuj|O#mGTdwlvSapY932^y;wN~6oGC zU)9~_VJyt_nbuZe<^|Q`7p9eLe ztjwg{uEq{gG<>HK-Acb(yQ!o{GIz}yOV%PfKEgseGo^waq~*^DS4S$p?I^;Rc>Jmm zs{N>V^0Z*D48!=2$QjXavTeG#Rk#zbVT2hVBGlZ_5gBu=YU}{_bu4?|MwPLXe&+R1 zRzcp0$B*~-F|=cr_;?|<8FE#C3lFGXjS(!qMp=8)eo-qTi18B=$dzMyNlN7 z54O-!SG(yvRrq1w!gW2}2WpeH=njrsarF&H0Klu~(0A!MpOn~7D(T_S#hTcBXfN_Q z-sMM0W#O(@ch*URNXzJ>rR~)%&-_*gJCI0@QJ<;1&Mw};?UeUC7nFXszNB-OmF@EG z%=(ub2>X1pP1O#%{qpJf9S5?lPn|<%k(VSuPdx#YclgsiqfGuhqiGz7OjH?71Ox`5 z)~&BCNYx!7b3GuvoszcLpOX@6{X<@F@JC`D<7`)z@93^NxO>klPv77zJ1Ln8nO}0M z<`*g#V-#E>YkC&R8v74NEXLH8^|0SoQA_Fl`va~(Ty6?{6Xs@g>@5)xFi+q5R8lZc zfY>3yiMg(eR@D9M700w7C;%;}oS}M?XIg)+vW94qeh-@*64@ifPK0=Y36P(mP@+7e zajy(ys^u7Bjhbf{1{Dl$#+BO%e{5F^_CLQ=LbdYlGctz7_=_+2OGoWZ9W0jfsoM$& zp&qc%OtF)eKpbTkA2pSBHT~g;o;|z>(~tx-WjGPiI&|h2zuFXX#veMb1o_MMkK_5v zp;kKngCY6zj!m;bv?-tRDF;%lPw_W4XfXK(Ix8VDCM;~4H5xKN0iNJEpcX%-aR5F$ zfyck>X(D>KL_2iOc!{%i`}7mB2I;FTu&@L5{pCKbtpU9Hizt%FR2&Ztz~GiA~6T=f#l3vbCNu0USpMU!~!+NP1xlLW;9a zF4FUZ_thu{{fxhgJe$_jU0t6RWh;>sX2>Zx_`|ASUD_^2R%H#iA9FmkNtG1tmK4J` ztK*yfsZMbkGR881?h*6CN=P6NkoZ^fjnj;$NEHf##n_5@B{ICr>+Ig<^X3faz}GZVs5KYKdf%Ht$)2A39gtSG&OhEB>cCT0z`P+w_?o)Ui$I<3G=BDQ&;Dz zLp$_Pyjw4$D$SjLD%Fr9iQgN3tbHY?pW1Ic-JY)hd7F52AX)Dm-|EfM)Y2rs?3StE z7#nKd0caB$G?G5%Dj&}TammWEBrWUkyGR!gf_N%vYoh&lppz4UZKKpQLq}RU69#4A4`{WfGj_dmMTO}Xd{-yIW?IEo< zV)aA-{o`{z&En=fgPZf7Co*;qltU;P>Y*9Rty!re3G@i4FzemRb0o+qo=W1+LrpW# z8p#W4bCVDMxG5EA+x@5zyJYKzOFP}r%|k^0K5pZf|%jz8>;7Q!b7?;zR` zkYQIYt08EPy}R=Dv>&~x_GE8ec21Q2SP zcjC1N+WrL!m5{n>LDpPZFLF60kUB+mLuLhO*vkYh=v6+;Gvr?4hOHXjuNP?46&rlY zFGtCH#B=n@3IE$voO$p;|JlWx=t3;9;Ta49*)8V zU1`3YLC^j`^_Ew}-EKDQKY=|S4M1=V1avmgN$pugAl^#I@x4GgP*NEqm38i(VQa27 z&4R>@-sFvrYQ=k;=~T9vo(z&k+I8IcHOJc4O3Yx`5%{ik{^f*s!1r?ZMBo!Q2TAnQ z>nz@b6mR2S8>L*_*clY`d$KP6I{-8UilXp1x8K99WYoWJ|G|)OIr$PNGFoLYQQnp3 zyqdhQyP#xcg#VzgnImj)d0|PfBSX% NMmm2eTmQ#8{{enkOH}{> literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/bip-bop-08.mp3 b/packages/ui/src/assets/audio/bip-bop-08.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..24af5b0a23800318973d625867aa44933dff1736 GIT binary patch literal 4390 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/bip-bop-10.mp3 b/packages/ui/src/assets/audio/bip-bop-10.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..056730a1f13f6c7334ee2f51dc3435886da74368 GIT binary patch literal 6760 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/nope-02.mp3 b/packages/ui/src/assets/audio/nope-02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8cdf890a1825524298d9f0346fd9196919b55844 GIT binary patch literal 5753 zcmeI0d05ihy2lrTh=>Y?1`cgRG$mBPxjIL1N{iY!wY5pmRJ1a*Y}gH=q&X%IDVlpn zmSmQtW@VX%V_H;dX6CW7EHj(T%xXJuVefnHbM`&wxqsZJbN{-Z=Kc>a8ARn}A~EP+-h> z+vT!)knyOIUL8OiR_j=c{+o`uLw2jnY72Y$dZ)C9BptS?UbZe1Eubl^gR}R1Wov1p zz5H}m*|A~Zr=-7~#L{+gX(*E%mK`NpQh>+zMG_RQcp5Z?Wrl}u9@lgXDVLZiXM}A5 zs-ZwmsqQy1CFA)BTncQ9t&H(Y3Hi|JxA?Is0E7Au`$KCE7z@X3>+eszym8>*I|&=T_`1uCEU#(TDu29kxWdX|MRM=`fRg=#xK{4iWJE z^3zIso)!II?2i?R@cf$NFi8#M*y&?`3lT0j#t;xWb5ZE)1_sI*dE0wH?>XxD?MWW` zTy1osb1g<&7&Wree|>=I!Icl57im1C(Xidn@hY-~#)`$plvu)QR(7V$_@F2aPQ%_T z{+#}!@ri3R-viU@Qc=*um=Vv1^<&qVel1^bRkgPWWx5PUmd!1b_@S!>{Q|jz z>^-xafxZ1Y>m86(e3(0s(dx5;-34hhrvTP9p)nt;2_?&w28gyY#9OH*4YrnO<70(J zHz$)C0gnqHV$A98p+%77H{E)2Qo^MtsbP8@iNOetPo2GiI8B#$Cqqo2-6UQe5CW z=fO!m_HIf3D4f&tdCaYf+%V;6o%ve!U~2pL7v;>cGwGl9oj>Ol^;0<&0EU@H1&i8W z=&b{GGQe2#tNbMzie67LD8_q6aeRoxaF{FIr9JH8a>paca}WqRi~|6gk94p$zg7k= z8&A35CZ5Q3Bm!};Pj!19EndC)JyI?kAUMrb>QZ zD@wYP6w<-I1jS()^!{j_5wxY*iDSv)ZsZZK0Sls2Szhh5P`=JQgyx@Ok#pcRop=bb zG~F_kDP$t`Cuw5O+Gra`O!>WfBHL#?$R>g$^)|tUrj({?bgvzz!6MTxGkwebXPDOeMZ*_)Cl zyRrt~3yo*-nHy*j19~vc9ZCy!;h$KXw`@$6kq}9iI-1r!$(`QTi~CfMDm|kh2h74f z5;Gx_NOaAFwt@vvD94@0+bd>vx*Uz+K(wws6MFWQ-W|ilgn#t0T6dc#t3&7##O}oo zN28xk9dh?@Nh&gjZ&Y;Fw+}{B3s?9$m>KCnFJ|6^$m;`)DNgtvaXhau6D_1PqfCSAdX`u9t!Z^?=(g?osytr?538mpN> z34`M>^e)1I-g+*HJMk<_>$QGTT6HLpQi8BWXZGNmv;duj#|e>Q?+^o9u>CaD9gMT<@c&Ria?`7#h)&d@DF?2KFA{#~COP23~=tP|yZ;5X`)i?3Ojv zDbNh0Mp9A+f86~g;%8@M%UL)CLOdjA+GE8lK=+v_8=2Gv+@_qX?K(?HNwvBQfCOV$ z*a3m#9jJ%>&F-u_*VW!=!`*5Mw9=Zf^P1H-SSE-l*;QH@*d66;sl2f}EyakPptXak3#ezt2tb9eUsfrbQFL2HJBUEfS(7qHv1 zgq;x0NX+?_OQWH(jc^{BxWV4k(xsg9VdBLOUo#r%#K;H?4>KT4K$l)DK;Vhk)I0-W z3cSb16qK$}&Q!PE5n9ss`hGhPWk+2Yb91gnY7zZf%ZQS8FCSsvaLeIjQGCCryqnEj z7V`r732%0_u$)l;=EM&WNG;K-IWy%iHoffho6IsU#LmXiQNTfESe~5sL_%P`j(Xcl z7d{i;(Ak5g;Cs7`{3|x57Voc&rytq-ev+_6|J$!y>|noLdff+?EShJm<9~}>UzPc| z(JtFzBn`6AL3PYVDP%yT`InLvA^Xo=0AKJYPuXF;qTck zun**VA6tx3EjcizIkLPf2hg2om-j6-*GbQEh;*dTnmh7!brfq!_Gwau$Hj2$ij3Ce z5hzMwOQsJ=9+Ale46p~uXrDlZYJ=A%57q-cpu3J<9=)D zw#w*zKJ|Gc3y;T{<;DiZF0p%3bK7RiqQu0N%ZvM~s~&q-$^9uauawF!`(G*NmzTE| z+;}{%yxy-=&dfB-zTo*wa!8l^_W1m=%ZCfOjIg+V(k9d0(%lGSyVW;!1DE%XTj>Xa z@zl{$9a(34w{}SoU@%7}+h|kwVI-H@PYVs(h^;3)vN#_8S8Es*+QPqZ_QiSHnarB)_57?L*{mSJ&~hB1-LH@^QhG@$6p zeYmi%6OCqZ1yE9_e-m-I@~5PpQ`*9fzJ)u6_C1+ToSia{MPtxZP1QB( zrMc~cZy(sdeRs9z`0ut+Dz9uhUiXE%(r$g@kJ}eL=>IsINUfcFU<|mb6r|c?n^C1g z=>i+5m$+;DmLjszaPMp#kZ>DtQ>FCpNtMaKwPXY#k>BBw2i`HQxF z(h`fzIQo5N8u&V=aIRTO#|fri5{w7M2+*D8AK%`(q;0AwJIj!wF#wUB#a#*mFKY2K z%P@!8n__q!00xJ_GA(@w7)_|i13GGFBr%cY z#E&HeN%(w|Y`9ZqUy3bN!ZqCngN;d0_Oou8`Yc9p%fW-qpu^!>UtyD;I> z8AQjk-yX;;N(CE#8oW)4aXR0=@A|tP`5_=_{#gVtZAg*uK1I$8n@i2j?qEJ&{1rTFA`&yu zJ}Odl6n`pE)&YTl719?mqLBh}sP^u&Y3`flyWS5z$R?vU*{=0Q0D2r!A}LX4yLUpequXDuHtEBf zNEU9ca^L_z{^Qy$35!I@4ISsEca!xSJ}vC;7Q4=0h`KtWsCnYNPkBK(yJQ4g^S7h+ z1y5pSL7|@heH7qA1{_$>=7sS$)kjNkM+Sv!7oi!r?(II(=Crj@?RMsQa1*m0R7M1e z&MlY8rKku4fI7lrw&V1fa6>Fo7Yc+>0G@zt@|s~rwD%q~i(`RL)fzU!8-$-*FYFkD zIaEM_zUT6E)P#-`A+BHOi1-16Hin_7SZ%ZwBA5=r6)zx-;1`EkF7?hepqsZ$0|Bj` z?v_rhvn=^#YnP?y+R(?zag9mc12O)6AH4RDUVPCk=*}#<)79!OyYYp!@^fXVGkda* z5UpAjGXX4wo$O^RT<5+|;A`D#)NmFG!k2PXk5Y(`fuD&^<0 zuv5sK#AZKCpaC}V)9p8gY}RTob-BSOPjwdogeNZ-G@r;>o1=U+k5|489XtQ`AJ8hl z8*KGS`YAA;%ePSNfVEB)kGeT@gZ~WVd+PlEU#jLzT^@NZgaK{5WI|P90wfP1fPbI4 z{F67-I{oT$&%DY=L;&36s&dc2Q|BMcO8;w}Bvr5gUTTj9X!!WQ9?y5t`BQ-ZS2}+Z z{jd4?{^R){b-tqy{xz)BB1mlB2F$=Cm+^KV)|f71C6ayEV7 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/nope-03.mp3 b/packages/ui/src/assets/audio/nope-03.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..42442317c372845d0f0f3dd82f35c567ed0beff5 GIT binary patch literal 3820 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�Se`zAXIp#WBy8fHHNha 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�mj z@?mq%;gCh7l$+_fCr$L&`=VFSrhbRa-Hfu7)Ha-UODr6YWXUlp zmoK%3TRW(E=3;uyt#(fRnR8uP?nC(}LVR&heb#Ww&)Ty_!*8WoY_&qs8nt`1Cl5FV zzbcnrFK9&TNyzmD25EQ-)aR6z{6ds0-}bli`yku2((kshxvOb3b$Igei{&xD!^c1M ze{~!FLL9&j4qDl#sc%=5?oW@Jh}$ib!lRN|I+2}mhWCUyj+}v}pzbWzJqj>eCB>TIg&7 z>s#Nvi|(=Y#G(n$frMPa%sgD!l*~vCCkw;F^RS&zMqHeP+R1Ck9;;kEzjr*&Qd#Lb z3v6#*#QOXY9CVwe)9HQLz5^v?LV;-|BeJmrchhQ>wl1#*8gv-uMgSEpV2hvrK+;uqA5NDK?WD<$5{9F@;!e%Qd~j`&?JnEcGCQ?8GbXnDtP`m~5iEzpGUK|K{H?#NW05 zu_2k;l+M=xTOFhQrt0^V!x_$B#1ex49?iex|6`&5OHuv(;{Oxn&wa8<*xo+b?q01B i20)9kw;%WSJN^y+|L7F{rK3#y57GIj=IuX3=f42xL9Lnq literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/nope-06.mp3 b/packages/ui/src/assets/audio/nope-06.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ed22c1cba2c66c97e76444cc66cd7b06f3bfd687 GIT binary patch literal 6066 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/nope-07.mp3 b/packages/ui/src/assets/audio/nope-07.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4b6dd462c80ca49e6c25015c28c0db2c74fc9e78 GIT binary patch literal 3459 zcmeH}dpuNI8^`yU8EIk+qLN!fZqYC^T|~LxO(x?qN{qXxjxKbOW?V8EMiU}b%gHOuT*G_x+sruXFk9{eJdd`&ny!)_R_`pY>aNJKEr2 zfEctpiDWB!A^`yE;vXHd5xX9@-q^_Kd*?y{&RX34x9WmP-m^bcWXXzd8PVkh;1_UT zBxaGc1+o@tT;$mTeTxh)@_K>EMWz=47Z7_E#TY1x(b!mA`4?I+iv?50>)GC=>fdc} zwxCsPK;}h-MyGT@NFoh@NQ@mv;kB7_7c_h#qhlHf;PTP6CqL{dVqroY&?|2@_o(E9 z`4`+yJQaJKnx%pxQ6W)BqoTuz^sxTNw99u_LRCw$ue~dR0~fjDl=sCf@9sEF70Jx$ zDL@$#86Hl+Z}8z@Bx!7jjh2G_wy>KHL25Xx_9lB0iI5?G;6$`Cbv`mC?a)|p4hH;b z*Da>BjBKGm76d;V+*;CA=m*Z{CLc{cmADBXj~uecOTyI{~i0^?CBeEP&xk74M&qQ5_DxD{P#8gMdiS=QUuU!8M>##?&z zttRPOFXMQ^1f2jK%rUh5qy6^!Hx=>@L_8TiQcN5*xp=z$x#!h)1D8@wd-w;lr*w21 zo%@2fI>JUT3*#m}==sc*xzq*(Hu+|~cz@s7!{jOB9SUZ z=iGigbz&f1Zuu)-GOa}Wz*OYveI46;p{h^===t-ZRB~9VSwV-TdMNxQxpTNcj*fsPL9F-j5GrpokXaB3 zcp>r=T|T6~{M4YJZiFV6wb}+Hv0?>$nZ#*zhYCCz-g(PB388sBsvdQ?(WJ{tR|6qY zPd)2ec%?#@)t7c(+wZy&T6pDv!LHmO+s{UT(T)_`SV6|2s?EVN!Lq)&EgN5`oGvzZ z-9BL=d#?$@bZ2+2iuE5q{?03TyuR+-bnlAwt#7y=qP(+vQ?*xLyI^d%Jjl5pJqB6s`a}}K`-n2jX1kO`O=%#e@#g;EwZ-Zw@B#)Bu?pK?oqyRwDM}S;Y zUlY>SO)S05%{(3y_DfXe9j6>H`0{532TiQQJO9zrsYHH8MKN0DRU;@-mWQOB!lSt55+gQ_|^-M8dRKD>Cax0b!L|J!&z zm>aSC@Vm~axfk!}UQLd>YFAFLuOkVGJE}&-QvF2J@71)NX{1RwmA|20zYY;vhSTZi zZLqp(kRTg2YjnooqK4mH^(#*xoi0mql@rr7-RFYB_r=)i@^5H<)qO5mRi=Z!zZ;`G zPPO_bc$gmB+{`TUazJfO_f3Bs5E)zbT#ZL=`YL2)FsS7tH+Zc?FcIXZN?WCXRKJ$n z>$^f%L3AF|4Yh{AkJ+hV>L9qEqfnA=T1g11t~!(B>Glwka!Gfzop)BU_VaCdn+HSXMluumSj*0$ z^KsAOjAX5)7&2zG?gXYTkvleOrb0oH5jw_JB*apu1iT$i__iG6Mh2&XJ2hA_kZx#j)iFUh~ffTNA@=DJ#g~Y*y z1`vEz9(73UnBLd+Ee?5muWxzl2d__ zMjgDb6z>VnqFTq|Eqx#e(U%MGga@D#DKHhzgTAWhKzEhB1xJ?4o?Yt}X_4=>bEYP* zOFjd(qbIbc=gueRnb1?-#*+C;vEkGB+CBC|(bYf2-tSo#5gQx&;_ckyeJ!`se47UX zo=3%hnV57J%MPSra@9>{I#`I9LS5?hNtja1S^3geitceGiIsA2tQAQbPxSEjEYqQW zRwZOv06Rn>VPaX+xsGj82#6)qP=BX#cY&l4E?6RB-^3H|IBy1$3rNMaoESqt<;+b? zboe#QT23IA90@6ZwxLW>KyKQr21vPYkv(|&fau!e(7Wl5}+ z2DZJrmi*hNp(=6GE~#e;Dej8Mq;vlvII-ydComWbKzzyj1|j|!bM-IdADdDvy9q#E zuLw7xQ4b+2qP6ZJDSv7i|5)gMU6f+k^+TJ@FbV+7N_2vn-vt2JpPK%U2aEmmLhmmA fm;wNv_BZ<}7D-mLp8xy#<9z&g>yP~W&F}4ZwM5#) literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/nope-08.mp3 b/packages/ui/src/assets/audio/nope-08.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e54577e43576977e1db835f5d61fe6bedd804e7e GIT binary patch literal 7450 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 Kf7$tO>Go$N6t)8Z literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/nope-10.mp3 b/packages/ui/src/assets/audio/nope-10.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0720993519ca211892aa69fd91d550a01b27b936 GIT binary patch literal 3981 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/audio/staplebops-01.mp3 b/packages/ui/src/assets/audio/staplebops-01.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b3b34a32fe8684b535ffb0d76255e62ef873da95 GIT binary patch literal 3665 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